lsst-ctrl-bps 29.2025.4700__tar.gz → 29.2025.4900__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.
Files changed (74) hide show
  1. {lsst_ctrl_bps-29.2025.4700/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.4900}/PKG-INFO +2 -2
  2. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/pyproject.toml +1 -1
  3. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/drivers.py +28 -14
  4. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/report.py +3 -14
  5. lsst_ctrl_bps-29.2025.4900/python/lsst/ctrl/bps/version.py +2 -0
  6. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900/python/lsst_ctrl_bps.egg-info}/PKG-INFO +2 -2
  7. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst_ctrl_bps.egg-info/requires.txt +1 -1
  8. lsst_ctrl_bps-29.2025.4900/tests/test_drivers.py +329 -0
  9. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_pre_transform.py +4 -2
  10. lsst_ctrl_bps-29.2025.4700/python/lsst/ctrl/bps/version.py +0 -2
  11. lsst_ctrl_bps-29.2025.4700/tests/test_drivers.py +0 -146
  12. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/COPYRIGHT +0 -0
  13. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/LICENSE +0 -0
  14. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/MANIFEST.in +0 -0
  15. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/README.md +0 -0
  16. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/bsd_license.txt +0 -0
  17. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/doc/lsst.ctrl.bps/CHANGES.rst +0 -0
  18. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/doc/lsst.ctrl.bps/index.rst +0 -0
  19. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/doc/lsst.ctrl.bps/quickstart.rst +0 -0
  20. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/gpl-v3.0.txt +0 -0
  21. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/__init__.py +0 -0
  22. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/__init__.py +0 -0
  23. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/__init__.py +0 -0
  24. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/_exceptions.py +0 -0
  25. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/bps_config.py +0 -0
  26. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/bps_draw.py +0 -0
  27. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/bps_reports.py +0 -0
  28. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/bps_utils.py +0 -0
  29. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cancel.py +0 -0
  30. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/__init__.py +0 -0
  31. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/bps.py +0 -0
  32. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/cmd/__init__.py +0 -0
  33. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/cmd/commands.py +0 -0
  34. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/opt/__init__.py +0 -0
  35. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/opt/arguments.py +0 -0
  36. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/opt/option_groups.py +0 -0
  37. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/cli/opt/options.py +0 -0
  38. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/clustered_quantum_graph.py +0 -0
  39. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/constants.py +0 -0
  40. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/construct.py +0 -0
  41. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/etc/bps_defaults.yaml +0 -0
  42. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/generic_workflow.py +0 -0
  43. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/initialize.py +0 -0
  44. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/ping.py +0 -0
  45. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/pre_transform.py +0 -0
  46. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/prepare.py +0 -0
  47. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/quantum_clustering_funcs.py +0 -0
  48. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/restart.py +0 -0
  49. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/status.py +0 -0
  50. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/submit.py +0 -0
  51. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/tests/config_test_utils.py +0 -0
  52. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/tests/gw_test_utils.py +0 -0
  53. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/transform.py +0 -0
  54. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst/ctrl/bps/wms_service.py +0 -0
  55. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst_ctrl_bps.egg-info/SOURCES.txt +0 -0
  56. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst_ctrl_bps.egg-info/dependency_links.txt +0 -0
  57. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst_ctrl_bps.egg-info/entry_points.txt +0 -0
  58. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst_ctrl_bps.egg-info/top_level.txt +0 -0
  59. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/python/lsst_ctrl_bps.egg-info/zip-safe +0 -0
  60. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/setup.cfg +0 -0
  61. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_bps_reports.py +0 -0
  62. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_bps_utils.py +0 -0
  63. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_bpsconfig.py +0 -0
  64. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_cli_commands.py +0 -0
  65. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_clustered_quantum_graph.py +0 -0
  66. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_construct.py +0 -0
  67. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_generic_workflow.py +0 -0
  68. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_initialize.py +0 -0
  69. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_ping.py +0 -0
  70. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_quantum_clustering_funcs.py +0 -0
  71. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_report.py +0 -0
  72. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_status.py +0 -0
  73. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_transform.py +0 -0
  74. {lsst_ctrl_bps-29.2025.4700 → lsst_ctrl_bps-29.2025.4900}/tests/test_wms_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-bps
3
- Version: 29.2025.4700
3
+ Version: 29.2025.4900
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-Expression: BSD-3-Clause OR GPL-3.0-or-later
@@ -23,7 +23,7 @@ License-File: gpl-v3.0.txt
23
23
  Requires-Dist: astropy>=4.0
24
24
  Requires-Dist: pyyaml>=5.1
25
25
  Requires-Dist: click>=7.0
26
- Requires-Dist: networkx
26
+ Requires-Dist: networkx<3.6
27
27
  Requires-Dist: lsst-daf-butler
28
28
  Requires-Dist: lsst-pipe-base
29
29
  Requires-Dist: lsst-ctrl-mpexec
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "astropy >=4.0",
28
28
  "pyyaml >=5.1",
29
29
  "click >= 7.0",
30
- "networkx",
30
+ "networkx <3.6",
31
31
  "lsst-daf-butler",
32
32
  "lsst-pipe-base",
33
33
  "lsst-ctrl-mpexec",
@@ -55,6 +55,7 @@ from lsst.utils.timer import time_this
55
55
  from lsst.utils.usage import get_peak_mem_usage
56
56
 
57
57
  from . import BPS_DEFAULTS, BPS_SEARCH_ORDER, DEFAULT_MEM_FMT, DEFAULT_MEM_UNIT, BpsConfig
58
+ from .bps_reports import compile_code_summary, compile_job_summary
58
59
  from .bps_utils import _dump_env_info, _dump_pkg_info, _make_id_link
59
60
  from .cancel import cancel
60
61
  from .construct import construct
@@ -68,7 +69,7 @@ from .initialize import (
68
69
  from .ping import ping
69
70
  from .pre_transform import acquire_quantum_graph, cluster_quanta
70
71
  from .prepare import prepare
71
- from .report import BPS_POSTPROCESSORS, display_report, retrieve_report
72
+ from .report import display_report, retrieve_report
72
73
  from .restart import restart
73
74
  from .status import status
74
75
  from .submit import submit
@@ -400,20 +401,31 @@ def restart_driver(wms_service, run_id):
400
401
  print("Restart failed: Unknown error")
401
402
 
402
403
 
403
- def report_driver(wms_service, run_id, user, hist_days, pass_thru, is_global=False, return_exit_codes=False):
404
- """Print out summary of jobs submitted for execution.
404
+ def report_driver(
405
+ wms_service: str | None = None,
406
+ run_id: str | None = None,
407
+ user: str | None = None,
408
+ hist_days: float = 0.0,
409
+ pass_thru: str | None = None,
410
+ is_global: bool = False,
411
+ return_exit_codes: bool = False,
412
+ ):
413
+ """Print out the summary of jobs submitted for execution.
405
414
 
406
415
  Parameters
407
416
  ----------
408
- wms_service : `str`
417
+ wms_service : `str`, optional
409
418
  Name of the class.
410
- run_id : `str`
419
+ run_id : `str`, optional
411
420
  A run id the report will be restricted to.
412
- user : `str`
421
+ user : `str`, optional
413
422
  A user the report will be restricted to.
414
- hist_days : `float`
415
- Number of days.
416
- pass_thru : `str`
423
+ hist_days : `float`, optional
424
+ Number of past days to consider while preparing the report. By default,
425
+ only the currently running workflows are included in the report.
426
+ If the report is restricted to a single run (i.e., ``run_id`` is set),
427
+ the history search will be limited by default to two past days.
428
+ pass_thru : `str`, optional
417
429
  A string to pass directly to the WMS service class.
418
430
  is_global : `bool`, optional
419
431
  If set, all available job queues will be queried for job information.
@@ -430,17 +442,19 @@ def report_driver(wms_service, run_id, user, hist_days, pass_thru, is_global=Fal
430
442
  Only applicable in the context of a WMS with associated
431
443
  handlers to return exit codes from jobs.
432
444
  """
433
- if wms_service is None:
445
+ if not wms_service:
434
446
  default_config = BpsConfig(BPS_DEFAULTS)
435
447
  wms_service = os.environ.get("BPS_WMS_SERVICE_CLASS", default_config["wmsServiceClass"])
436
448
 
437
- # When reporting on single run:
438
- # * increase history until better mechanism for handling completed jobs is
439
- # available.
449
+ # When reporting on a single run:
450
+ # * increase history until a better mechanism for handling completed jobs
451
+ # is available.
440
452
  # * massage the retrieved reports using BPS report postprocessors.
441
453
  if run_id:
442
454
  hist_days = max(hist_days, 2)
443
- postprocessors = BPS_POSTPROCESSORS
455
+ postprocessors = [compile_job_summary]
456
+ if return_exit_codes:
457
+ postprocessors.append(compile_code_summary)
444
458
  else:
445
459
  postprocessors = None
446
460
 
@@ -31,7 +31,7 @@ Note: Expectations are that future reporting effort will revolve around LSST
31
31
  oriented database tables.
32
32
  """
33
33
 
34
- __all__ = ["BPS_POSTPROCESSORS", "display_report", "retrieve_report"]
34
+ __all__ = ["display_report", "retrieve_report"]
35
35
 
36
36
  import logging
37
37
  import sys
@@ -40,20 +40,9 @@ from typing import TextIO
40
40
 
41
41
  from lsst.utils import doImportType
42
42
 
43
- from .bps_reports import (
44
- DetailedRunReport,
45
- ExitCodesReport,
46
- SummaryRunReport,
47
- compile_code_summary,
48
- compile_job_summary,
49
- )
43
+ from .bps_reports import DetailedRunReport, ExitCodesReport, SummaryRunReport
50
44
  from .wms_service import BaseWmsService, WmsRunReport, WmsStates
51
45
 
52
- BPS_POSTPROCESSORS = (compile_job_summary, compile_code_summary)
53
- """Postprocessors for massaging run reports
54
- (`tuple` [`Callable` [[`WmsRunReport`], None]).
55
- """
56
-
57
46
  _LOG = logging.getLogger(__name__)
58
47
 
59
48
 
@@ -167,7 +156,7 @@ def retrieve_report(
167
156
  pass_thru: str | None = None,
168
157
  is_global: bool = False,
169
158
  return_exit_codes: bool = False,
170
- postprocessors: Sequence[Callable[[WmsRunReport], None]] | None = None,
159
+ postprocessors: Sequence[Callable[[WmsRunReport], list[str]]] | None = None,
171
160
  ) -> tuple[list[WmsRunReport], list[str]]:
172
161
  """Retrieve summary of jobs submitted for execution.
173
162
 
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "29.2025.4900"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-bps
3
- Version: 29.2025.4700
3
+ Version: 29.2025.4900
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-Expression: BSD-3-Clause OR GPL-3.0-or-later
@@ -23,7 +23,7 @@ License-File: gpl-v3.0.txt
23
23
  Requires-Dist: astropy>=4.0
24
24
  Requires-Dist: pyyaml>=5.1
25
25
  Requires-Dist: click>=7.0
26
- Requires-Dist: networkx
26
+ Requires-Dist: networkx<3.6
27
27
  Requires-Dist: lsst-daf-butler
28
28
  Requires-Dist: lsst-pipe-base
29
29
  Requires-Dist: lsst-ctrl-mpexec
@@ -1,7 +1,7 @@
1
1
  astropy>=4.0
2
2
  pyyaml>=5.1
3
3
  click>=7.0
4
- networkx
4
+ networkx<3.6
5
5
  lsst-daf-butler
6
6
  lsst-pipe-base
7
7
  lsst-ctrl-mpexec
@@ -0,0 +1,329 @@
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
+ """Unit tests for drivers.py."""
28
+
29
+ import logging
30
+ import os
31
+ import shutil
32
+ import tempfile
33
+ import unittest
34
+
35
+ import yaml
36
+
37
+ from lsst.ctrl.bps import WmsRunReport, WmsStates
38
+ from lsst.ctrl.bps.bps_reports import compile_code_summary, compile_job_summary
39
+ from lsst.ctrl.bps.drivers import _init_submission_driver, ping_driver, report_driver, status_driver
40
+
41
+ TESTDIR = os.path.abspath(os.path.dirname(__file__))
42
+
43
+
44
+ class TestInitSubmissionDriver(unittest.TestCase):
45
+ """Test submission."""
46
+
47
+ def setUp(self):
48
+ self.cwd = os.getcwd()
49
+ self.tmpdir = tempfile.mkdtemp(dir=TESTDIR)
50
+
51
+ def tearDown(self):
52
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
53
+
54
+ @unittest.mock.patch("lsst.ctrl.bps.initialize.BPS_DEFAULTS", {})
55
+ def testDeprecatedOutCollection(self):
56
+ config = {
57
+ "submitPath": "bad",
58
+ "payload": {
59
+ "outCollection": "bad",
60
+ "outputRun": "bad",
61
+ },
62
+ }
63
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".yaml") as file:
64
+ yaml.dump(config, stream=file)
65
+ with self.assertRaisesRegex(KeyError, "outCollection"):
66
+ _init_submission_driver(file.name)
67
+
68
+ @unittest.mock.patch("lsst.ctrl.bps.initialize.BPS_DEFAULTS", {})
69
+ def testMissingOutputRun(self):
70
+ config = {"submitPath": "bad"}
71
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".yaml") as file:
72
+ yaml.dump(config, stream=file)
73
+ with self.assertRaisesRegex(KeyError, "outputRun"):
74
+ _init_submission_driver(file.name)
75
+
76
+ @unittest.mock.patch("lsst.ctrl.bps.initialize.BPS_DEFAULTS", {})
77
+ def testMissingSubmitPath(self):
78
+ config = {"payload": {"outputRun": "bad"}}
79
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".yaml") as file:
80
+ yaml.dump(config, stream=file)
81
+ with self.assertRaisesRegex(KeyError, "submitPath"):
82
+ _init_submission_driver(file.name)
83
+
84
+
85
+ class TestPingDriver(unittest.TestCase):
86
+ """Test ping."""
87
+
88
+ def testWmsServiceSuccess(self):
89
+ retval = ping_driver("wms_test_utils.WmsServiceSuccess")
90
+ self.assertEqual(retval, 0)
91
+
92
+ def testWmsServiceFailure(self):
93
+ with self.assertLogs(level=logging.ERROR) as cm:
94
+ retval = ping_driver("wms_test_utils.WmsServiceFailure")
95
+ self.assertNotEqual(retval, 0)
96
+ self.assertEqual(cm.records[0].getMessage(), "Couldn't contact service X")
97
+
98
+ def testWmsServiceEnvVar(self):
99
+ with unittest.mock.patch.dict(
100
+ os.environ, {"BPS_WMS_SERVICE_CLASS": "wms_test_utils.WmsServiceSuccess"}
101
+ ):
102
+ retval = ping_driver()
103
+ self.assertEqual(retval, 0)
104
+
105
+ @unittest.mock.patch(
106
+ "lsst.ctrl.bps.drivers.BPS_DEFAULTS", {"wmsServiceClass": "wms_test_utils.WmsServiceDefault"}
107
+ )
108
+ def testWmsServiceNone(self):
109
+ with unittest.mock.patch.dict(os.environ, {}):
110
+ with self.assertLogs(level=logging.INFO) as cm:
111
+ retval = ping_driver()
112
+ self.assertEqual(retval, 0)
113
+ self.assertEqual(cm.records[0].getMessage(), "DEFAULT None")
114
+
115
+ def testWmsServicePassThru(self):
116
+ with self.assertLogs(level=logging.INFO) as cm:
117
+ retval = ping_driver("wms_test_utils.WmsServicePassThru", "EXTRA_VALUES")
118
+ self.assertEqual(retval, 0)
119
+ self.assertRegex(cm.output[0], "INFO.+EXTRA_VALUES")
120
+
121
+
122
+ class TestStatusDriver(unittest.TestCase):
123
+ """Test status_driver function."""
124
+
125
+ def testWmsServiceSuccess(self):
126
+ with self.assertLogs(level=logging.INFO) as cm:
127
+ retval = status_driver("wms_test_utils.WmsServiceSuccess", run_id="/dummy/path", hist_days=3)
128
+ self.assertEqual(retval, WmsStates.SUCCEEDED.value)
129
+ self.assertEqual(cm.records[0].getMessage(), "status: SUCCEEDED")
130
+
131
+ def testWmsServiceFailure(self):
132
+ with self.assertLogs(level=logging.WARNING) as cm:
133
+ retval = status_driver("wms_test_utils.WmsServiceFailure", run_id="/dummy/path", hist_days=3)
134
+ self.assertEqual(retval, WmsStates.FAILED.value)
135
+ self.assertEqual(cm.records[0].getMessage(), "Dummy error message.")
136
+
137
+ @unittest.mock.patch(
138
+ "lsst.ctrl.bps.drivers.BPS_DEFAULTS", {"wmsServiceClass": "wms_test_utils.WmsServiceDefault"}
139
+ )
140
+ def testWmsServiceNone(self):
141
+ with unittest.mock.patch.dict(os.environ, {}):
142
+ retval = status_driver(None, run_id="/dummy/path", hist_days=3)
143
+ self.assertEqual(retval, WmsStates.RUNNING.value)
144
+
145
+
146
+ class TestReportDriver(unittest.TestCase):
147
+ """Test report_driver function."""
148
+
149
+ @unittest.mock.patch(
150
+ "lsst.ctrl.bps.drivers.BPS_DEFAULTS", new={"wmsServiceClass": "wms_test_utils.WmsServiceSuccess"}
151
+ )
152
+ def testWmsServiceFromDefaults(self):
153
+ # Should not raise an exception and use default from BPS_DEFAULTS.
154
+ with unittest.mock.patch.dict(os.environ, {}, clear=True):
155
+ report_driver(
156
+ wms_service=None,
157
+ run_id=None,
158
+ user=None,
159
+ hist_days=0,
160
+ pass_thru=None,
161
+ )
162
+
163
+ def testWmsServiceFromEnvVar(self):
164
+ # Should not raise an exception.
165
+ with unittest.mock.patch.dict(
166
+ os.environ, {"BPS_WMS_SERVICE_CLASS": "wms_test_utils.WmsServiceSuccess"}
167
+ ):
168
+ report_driver(
169
+ wms_service=None,
170
+ run_id=None,
171
+ user=None,
172
+ hist_days=0.0,
173
+ pass_thru=None,
174
+ )
175
+
176
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
177
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
178
+ def testHistDefault(self, mock_display, mock_retrieve):
179
+ mock_retrieve.return_value = ([], [])
180
+
181
+ report_driver(
182
+ wms_service="wms_test_utils.WmsServiceSuccess",
183
+ run_id="123",
184
+ user=None,
185
+ hist_days=0.0,
186
+ pass_thru=None,
187
+ )
188
+
189
+ # Verify retrieve_report was called with the default hist setting.
190
+ _, kwargs = mock_retrieve.call_args
191
+ self.assertAlmostEqual(kwargs["hist"], 2.0)
192
+
193
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
194
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
195
+ def testHistCustom(self, mock_display, mock_retrieve):
196
+ mock_retrieve.return_value = ([], [])
197
+
198
+ report_driver(
199
+ wms_service="wms_test_utils.WmsServiceSuccess",
200
+ run_id="123",
201
+ user=None,
202
+ hist_days=4.0,
203
+ pass_thru=None,
204
+ )
205
+
206
+ # Verify retrieve_report was called with a custom hist setting.
207
+ _, kwargs = mock_retrieve.call_args
208
+ self.assertAlmostEqual(kwargs["hist"], 4.0)
209
+
210
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
211
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
212
+ def testPostprocessorsWithoutExitCodes(self, mock_display, mock_retrieve):
213
+ mock_retrieve.return_value = ([], [])
214
+
215
+ report_driver(
216
+ wms_service="wms_test_utils.WmsServiceSuccess",
217
+ run_id="123",
218
+ user=None,
219
+ hist_days=0.0,
220
+ pass_thru=None,
221
+ return_exit_codes=False,
222
+ )
223
+
224
+ # Verify the postprocessors list contains only one postprocessor.
225
+ args, kwargs = mock_retrieve.call_args
226
+ self.assertEqual(len(kwargs["postprocessors"]), 1)
227
+ self.assertIn(compile_job_summary, kwargs["postprocessors"])
228
+
229
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
230
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
231
+ def testPostprocessorsWithExitCodes(self, mock_display, mock_retrieve):
232
+ mock_retrieve.return_value = ([], [])
233
+
234
+ report_driver(
235
+ wms_service="wms_test_utils.WmsServiceSuccess",
236
+ run_id="123",
237
+ user=None,
238
+ hist_days=0.0,
239
+ pass_thru=None,
240
+ return_exit_codes=True,
241
+ )
242
+
243
+ # Verify the postprocessors list contains both postprocessors.
244
+ _, kwargs = mock_retrieve.call_args
245
+ self.assertEqual(len(kwargs["postprocessors"]), 2)
246
+ self.assertIn(compile_code_summary, kwargs["postprocessors"])
247
+ self.assertIn(compile_job_summary, kwargs["postprocessors"])
248
+
249
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
250
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
251
+ def testPostprocessorsNoRunId(self, mock_display, mock_retrieve):
252
+ mock_retrieve.return_value = ([], [])
253
+
254
+ report_driver(
255
+ wms_service="wms_test_utils.WmsServiceSuccess",
256
+ run_id=None,
257
+ user=None,
258
+ hist_days=0.0,
259
+ pass_thru=None,
260
+ )
261
+
262
+ # Verify postprocessors contains compile_job_summary
263
+ _, kwargs = mock_retrieve.call_args
264
+ self.assertIsNone(kwargs["postprocessors"])
265
+
266
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
267
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
268
+ def testDisplayCalledIfRuns(self, mock_display, mock_retrieve):
269
+ mock_runs = [WmsRunReport(wms_id="1", state=WmsStates.SUCCEEDED)]
270
+ mock_retrieve.return_value = (mock_runs, [])
271
+
272
+ report_driver(
273
+ wms_service="wms_test_utils.WmsServiceSuccess",
274
+ run_id=None,
275
+ user=None,
276
+ hist_days=0,
277
+ pass_thru=None,
278
+ )
279
+
280
+ # Verify display_report was called with the runs
281
+ mock_display.assert_called_once()
282
+ args, kwargs = mock_display.call_args
283
+ self.assertEqual(args[0], mock_runs)
284
+
285
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
286
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
287
+ def testDisplayCalledIfMessages(self, mock_display, mock_retrieve):
288
+ mock_messages = ["Warning message 1", "Warning message 2"]
289
+ mock_retrieve.return_value = ([], mock_messages)
290
+
291
+ report_driver(
292
+ wms_service="wms_test_utils.WmsServiceSuccess",
293
+ run_id=None,
294
+ user=None,
295
+ hist_days=0,
296
+ pass_thru=None,
297
+ )
298
+
299
+ # Verify display_report was called with messages
300
+ mock_display.assert_called_once()
301
+ args, kwargs = mock_display.call_args
302
+ self.assertEqual(args[1], mock_messages)
303
+
304
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.retrieve_report")
305
+ @unittest.mock.patch("lsst.ctrl.bps.drivers.display_report")
306
+ @unittest.mock.patch("builtins.print")
307
+ def testNoRecordsFoundMessage(self, mock_print, mock_display, mock_retrieve):
308
+ mock_retrieve.return_value = ([], [])
309
+
310
+ report_driver(
311
+ wms_service="wms_test_utils.WmsServiceSuccess",
312
+ run_id="123",
313
+ user=None,
314
+ hist_days=1.5,
315
+ pass_thru=None,
316
+ )
317
+
318
+ # Verify display_report() was NOT called.
319
+ mock_display.assert_not_called()
320
+
321
+ # Verify that a helpful message was printed.
322
+ mock_print.assert_called_once()
323
+ call_args = mock_print.call_args[0][0]
324
+ self.assertIn("No records found", call_args)
325
+ self.assertIn("123", call_args)
326
+
327
+
328
+ if __name__ == "__main__":
329
+ unittest.main()
@@ -194,7 +194,8 @@ class TestClusterQuanta(unittest.TestCase):
194
194
  "validateClusteredQgraph": True,
195
195
  }
196
196
  config = BpsConfig(settings, search_order=[])
197
- qgraph = InMemoryRepo().make_quantum_graph()
197
+ with InMemoryRepo() as repo:
198
+ qgraph = repo.make_quantum_graph()
198
199
  with self.assertRaisesRegex(RuntimeError, "Fake error"):
199
200
  _ = cluster_quanta(config, qgraph, "a_name")
200
201
 
@@ -208,7 +209,8 @@ class TestClusterQuanta(unittest.TestCase):
208
209
  "validateClusteredQgraph": False,
209
210
  }
210
211
  config = BpsConfig(settings, search_order=[])
211
- qgraph = InMemoryRepo().make_quantum_graph()
212
+ with InMemoryRepo() as repo:
213
+ qgraph = repo.make_quantum_graph()
212
214
  _ = cluster_quanta(config, qgraph, "a_name")
213
215
 
214
216
 
@@ -1,2 +0,0 @@
1
- __all__ = ["__version__"]
2
- __version__ = "29.2025.4700"
@@ -1,146 +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
- """Unit tests for drivers.py."""
28
-
29
- import logging
30
- import os
31
- import shutil
32
- import tempfile
33
- import unittest
34
-
35
- import yaml
36
-
37
- from lsst.ctrl.bps import WmsStates
38
- from lsst.ctrl.bps.drivers import _init_submission_driver, ping_driver, status_driver
39
-
40
- TESTDIR = os.path.abspath(os.path.dirname(__file__))
41
-
42
-
43
- class TestInitSubmissionDriver(unittest.TestCase):
44
- """Test submission."""
45
-
46
- def setUp(self):
47
- self.cwd = os.getcwd()
48
- self.tmpdir = tempfile.mkdtemp(dir=TESTDIR)
49
-
50
- def tearDown(self):
51
- shutil.rmtree(self.tmpdir, ignore_errors=True)
52
-
53
- @unittest.mock.patch("lsst.ctrl.bps.initialize.BPS_DEFAULTS", {})
54
- def testDeprecatedOutCollection(self):
55
- config = {
56
- "submitPath": "bad",
57
- "payload": {
58
- "outCollection": "bad",
59
- "outputRun": "bad",
60
- },
61
- }
62
- with tempfile.NamedTemporaryFile(mode="w+", suffix=".yaml") as file:
63
- yaml.dump(config, stream=file)
64
- with self.assertRaisesRegex(KeyError, "outCollection"):
65
- _init_submission_driver(file.name)
66
-
67
- @unittest.mock.patch("lsst.ctrl.bps.initialize.BPS_DEFAULTS", {})
68
- def testMissingOutputRun(self):
69
- config = {"submitPath": "bad"}
70
- with tempfile.NamedTemporaryFile(mode="w+", suffix=".yaml") as file:
71
- yaml.dump(config, stream=file)
72
- with self.assertRaisesRegex(KeyError, "outputRun"):
73
- _init_submission_driver(file.name)
74
-
75
- @unittest.mock.patch("lsst.ctrl.bps.initialize.BPS_DEFAULTS", {})
76
- def testMissingSubmitPath(self):
77
- config = {"payload": {"outputRun": "bad"}}
78
- with tempfile.NamedTemporaryFile(mode="w+", suffix=".yaml") as file:
79
- yaml.dump(config, stream=file)
80
- with self.assertRaisesRegex(KeyError, "submitPath"):
81
- _init_submission_driver(file.name)
82
-
83
-
84
- class TestPingDriver(unittest.TestCase):
85
- """Test ping."""
86
-
87
- def testWmsServiceSuccess(self):
88
- retval = ping_driver("wms_test_utils.WmsServiceSuccess")
89
- self.assertEqual(retval, 0)
90
-
91
- def testWmsServiceFailure(self):
92
- with self.assertLogs(level=logging.ERROR) as cm:
93
- retval = ping_driver("wms_test_utils.WmsServiceFailure")
94
- self.assertNotEqual(retval, 0)
95
- self.assertEqual(cm.records[0].getMessage(), "Couldn't contact service X")
96
-
97
- def testWmsServiceEnvVar(self):
98
- with unittest.mock.patch.dict(
99
- os.environ, {"BPS_WMS_SERVICE_CLASS": "wms_test_utils.WmsServiceSuccess"}
100
- ):
101
- retval = ping_driver()
102
- self.assertEqual(retval, 0)
103
-
104
- @unittest.mock.patch(
105
- "lsst.ctrl.bps.drivers.BPS_DEFAULTS", {"wmsServiceClass": "wms_test_utils.WmsServiceDefault"}
106
- )
107
- def testWmsServiceNone(self):
108
- with unittest.mock.patch.dict(os.environ, {}):
109
- with self.assertLogs(level=logging.INFO) as cm:
110
- retval = ping_driver()
111
- self.assertEqual(retval, 0)
112
- self.assertEqual(cm.records[0].getMessage(), "DEFAULT None")
113
-
114
- def testWmsServicePassThru(self):
115
- with self.assertLogs(level=logging.INFO) as cm:
116
- retval = ping_driver("wms_test_utils.WmsServicePassThru", "EXTRA_VALUES")
117
- self.assertEqual(retval, 0)
118
- self.assertRegex(cm.output[0], "INFO.+EXTRA_VALUES")
119
-
120
-
121
- class TestStatusDriver(unittest.TestCase):
122
- """Test status_driver function."""
123
-
124
- def testWmsServiceSuccess(self):
125
- with self.assertLogs(level=logging.INFO) as cm:
126
- retval = status_driver("wms_test_utils.WmsServiceSuccess", run_id="/dummy/path", hist_days=3)
127
- self.assertEqual(retval, WmsStates.SUCCEEDED.value)
128
- self.assertEqual(cm.records[0].getMessage(), "status: SUCCEEDED")
129
-
130
- def testWmsServiceFailure(self):
131
- with self.assertLogs(level=logging.WARNING) as cm:
132
- retval = status_driver("wms_test_utils.WmsServiceFailure", run_id="/dummy/path", hist_days=3)
133
- self.assertEqual(retval, WmsStates.FAILED.value)
134
- self.assertEqual(cm.records[0].getMessage(), "Dummy error message.")
135
-
136
- @unittest.mock.patch(
137
- "lsst.ctrl.bps.drivers.BPS_DEFAULTS", {"wmsServiceClass": "wms_test_utils.WmsServiceDefault"}
138
- )
139
- def testWmsServiceNone(self):
140
- with unittest.mock.patch.dict(os.environ, {}):
141
- retval = status_driver(None, run_id="/dummy/path", hist_days=3)
142
- self.assertEqual(retval, WmsStates.RUNNING.value)
143
-
144
-
145
- if __name__ == "__main__":
146
- unittest.main()