lsst-ctrl-bps 29.2025.3700__tar.gz → 29.2025.3900__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.3700/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.3900}/PKG-INFO +1 -1
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/bps_reports.py +131 -43
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/report.py +38 -19
- lsst_ctrl_bps-29.2025.3900/python/lsst/ctrl/bps/version.py +2 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900/python/lsst_ctrl_bps.egg-info}/PKG-INFO +1 -1
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/SOURCES.txt +1 -0
- lsst_ctrl_bps-29.2025.3700/tests/test_report.py → lsst_ctrl_bps-29.2025.3900/tests/test_bps_reports.py +118 -39
- lsst_ctrl_bps-29.2025.3900/tests/test_report.py +72 -0
- lsst_ctrl_bps-29.2025.3700/python/lsst/ctrl/bps/version.py +0 -2
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/COPYRIGHT +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/LICENSE +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/MANIFEST.in +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/README.md +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/bsd_license.txt +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/doc/lsst.ctrl.bps/CHANGES.rst +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/doc/lsst.ctrl.bps/index.rst +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/doc/lsst.ctrl.bps/quickstart.rst +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/gpl-v3.0.txt +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/pyproject.toml +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/_exceptions.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/bps_config.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/bps_draw.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/bps_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cancel.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/bps.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/cmd/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/cmd/commands.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/arguments.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/option_groups.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/options.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/clustered_quantum_graph.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/constants.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/construct.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/drivers.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/etc/bps_defaults.yaml +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/initialize.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/ping.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/pre_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/prepare.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/quantum_clustering_funcs.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/restart.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/status.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/submit.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/tests/config_test_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/tests/gw_test_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/transform.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/wms_service.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/dependency_links.txt +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/entry_points.txt +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/requires.txt +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/top_level.txt +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/zip-safe +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/setup.cfg +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_bps_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_bpsconfig.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_cli_commands.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_clustered_quantum_graph.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_construct.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_drivers.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_initialize.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_ping.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_pre_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_quantum_clustering_funcs.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_status.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_wms_service.py +0 -0
{lsst_ctrl_bps-29.2025.3700/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.3900}/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.3900
|
|
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
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/bps_reports.py
RENAMED
|
@@ -27,7 +27,14 @@
|
|
|
27
27
|
|
|
28
28
|
"""Classes and functions used in reporting run status."""
|
|
29
29
|
|
|
30
|
-
__all__ = [
|
|
30
|
+
__all__ = [
|
|
31
|
+
"BaseRunReport",
|
|
32
|
+
"DetailedRunReport",
|
|
33
|
+
"ExitCodesReport",
|
|
34
|
+
"SummaryRunReport",
|
|
35
|
+
"compile_code_summary",
|
|
36
|
+
"compile_job_summary",
|
|
37
|
+
]
|
|
31
38
|
|
|
32
39
|
import abc
|
|
33
40
|
import logging
|
|
@@ -55,7 +62,7 @@ class BaseRunReport(abc.ABC):
|
|
|
55
62
|
|
|
56
63
|
def __eq__(self, other):
|
|
57
64
|
if isinstance(other, BaseRunReport):
|
|
58
|
-
return
|
|
65
|
+
return self._table.pformat() == other._table.pformat()
|
|
59
66
|
return False
|
|
60
67
|
|
|
61
68
|
def __len__(self):
|
|
@@ -195,7 +202,7 @@ class DetailedRunReport(BaseRunReport):
|
|
|
195
202
|
job_summary = run_report.job_summary
|
|
196
203
|
if job_summary is None:
|
|
197
204
|
id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
|
|
198
|
-
self._msg = f"WARNING: Job summary for run '{id_}' not available, report
|
|
205
|
+
self._msg = f"WARNING: Job summary for run '{id_}' not available, report may be incomplete."
|
|
199
206
|
return
|
|
200
207
|
|
|
201
208
|
if by_label_expected:
|
|
@@ -231,44 +238,60 @@ class ExitCodesReport(BaseRunReport):
|
|
|
231
238
|
error handling from the wms service.
|
|
232
239
|
"""
|
|
233
240
|
|
|
234
|
-
def add(self, run_report, use_global_id=False):
|
|
241
|
+
def add(self, run_report: WmsRunReport, use_global_id: bool = False) -> None:
|
|
235
242
|
# Docstring inherited from the base class.
|
|
236
243
|
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
exit_code_summary = run_report.exit_code_summary
|
|
245
|
+
if not exit_code_summary:
|
|
246
|
+
id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
|
|
247
|
+
self._msg = f"WARNING: Exit code summary for run '{id_}' not available, report may be incomplete."
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
warnings = []
|
|
251
|
+
|
|
252
|
+
# If available, use label ordering from the run summary as it should
|
|
253
|
+
# reflect the ordering of the pipetasks in the pipeline.
|
|
239
254
|
labels = []
|
|
240
255
|
if run_report.run_summary:
|
|
241
256
|
for part in run_report.run_summary.split(";"):
|
|
242
257
|
label, _ = part.split(":")
|
|
243
258
|
labels.append(label)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return
|
|
259
|
+
if not labels:
|
|
260
|
+
labels = sorted(exit_code_summary)
|
|
261
|
+
warnings.append("WARNING: Could not determine order of pipeline, instead sorted alphabetically.")
|
|
248
262
|
|
|
249
263
|
# Payload (e.g. pipetask) error codes:
|
|
250
264
|
# * 1: general failure,
|
|
251
265
|
# * 2: command line error (e.g. unknown command and/or option).
|
|
252
266
|
pyld_error_codes = {1, 2}
|
|
253
267
|
|
|
254
|
-
|
|
268
|
+
missing_labels = set()
|
|
255
269
|
for label in labels:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
270
|
+
try:
|
|
271
|
+
exit_codes = exit_code_summary[label]
|
|
272
|
+
except KeyError:
|
|
273
|
+
missing_labels.add(label)
|
|
274
|
+
else:
|
|
275
|
+
pyld_errors = [code for code in exit_codes if code in pyld_error_codes]
|
|
276
|
+
pyld_error_count = len(pyld_errors)
|
|
277
|
+
pyld_error_summary = (
|
|
278
|
+
", ".join(sorted(str(code) for code in set(pyld_errors))) if pyld_errors else "None"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
infra_errors = [code for code in exit_codes if code not in pyld_error_codes]
|
|
282
|
+
infra_error_count = len(infra_errors)
|
|
283
|
+
infra_error_summary = (
|
|
284
|
+
", ".join(sorted(str(code) for code in set(infra_errors))) if infra_errors else "None"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
run = [label, pyld_error_count, pyld_error_summary, infra_error_count, infra_error_summary]
|
|
288
|
+
self._table.add_row(run)
|
|
289
|
+
if missing_labels:
|
|
290
|
+
warnings.append(
|
|
291
|
+
f"WARNING: Exit code summary was not available for job labels: {', '.join(missing_labels)}"
|
|
268
292
|
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
self._table.add_row(run)
|
|
293
|
+
if warnings:
|
|
294
|
+
self._msg = "\n".join(warnings)
|
|
272
295
|
|
|
273
296
|
def __str__(self):
|
|
274
297
|
alignments = ["<"] + [">"] * (len(self._table.colnames) - 1)
|
|
@@ -276,7 +299,7 @@ class ExitCodesReport(BaseRunReport):
|
|
|
276
299
|
return str("\n".join(lines))
|
|
277
300
|
|
|
278
301
|
|
|
279
|
-
def compile_job_summary(report: WmsRunReport) ->
|
|
302
|
+
def compile_job_summary(report: WmsRunReport) -> list[str]:
|
|
280
303
|
"""Add a job summary to the run report if necessary.
|
|
281
304
|
|
|
282
305
|
If the job summary is not provided, the function will attempt to compile
|
|
@@ -289,24 +312,89 @@ def compile_job_summary(report: WmsRunReport) -> None:
|
|
|
289
312
|
report : `lsst.ctrl.bps.WmsRunReport`
|
|
290
313
|
Information about a single run.
|
|
291
314
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
warnings : `list` [`str`]
|
|
318
|
+
List of messages describing any non-critical issues encountered during
|
|
319
|
+
processing. Empty if none.
|
|
297
320
|
"""
|
|
321
|
+
warnings: list[str] = []
|
|
322
|
+
|
|
323
|
+
# If the job summary already exists, exit early.
|
|
298
324
|
if report.job_summary:
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
325
|
+
return warnings
|
|
326
|
+
|
|
327
|
+
if report.jobs:
|
|
328
|
+
job_summary = {}
|
|
329
|
+
by_label = group_jobs_by_label(report.jobs)
|
|
330
|
+
for label, job_group in by_label.items():
|
|
331
|
+
by_label_state = group_jobs_by_state(job_group)
|
|
332
|
+
_LOG.debug("by_label_state = %s", by_label_state)
|
|
333
|
+
counts = {state: len(jobs) for state, jobs in by_label_state.items()}
|
|
334
|
+
job_summary[label] = counts
|
|
335
|
+
report.job_summary = job_summary
|
|
336
|
+
else:
|
|
337
|
+
warnings.append("information about individual jobs not available")
|
|
338
|
+
|
|
339
|
+
return warnings
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def compile_code_summary(report: WmsRunReport) -> list[str]:
|
|
343
|
+
"""Add missing entries to the exit code summary if necessary.
|
|
344
|
+
|
|
345
|
+
A WMS plugin may exclude job labels for which there are no failures from
|
|
346
|
+
the exit code summary. The function will attempt to use the job summary,
|
|
347
|
+
if available, to add missing entries for these labels.
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
351
|
+
report : `lsst.ctrl.bps.WmsRunReport`
|
|
352
|
+
Information about a single run.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
warnings : `list` [`str`]
|
|
357
|
+
List of messages describing any non-critical issues encountered during
|
|
358
|
+
processing. Empty if none.
|
|
359
|
+
"""
|
|
360
|
+
warnings: list[str] = []
|
|
361
|
+
|
|
362
|
+
# If the job summary is not available, exit early.
|
|
363
|
+
if not report.job_summary:
|
|
364
|
+
return warnings
|
|
365
|
+
|
|
366
|
+
# A shallow copy is enough here because we won't be modifying the existing
|
|
367
|
+
# entries, only adding new ones if necessary.
|
|
368
|
+
exit_code_summary = dict(report.exit_code_summary) if report.exit_code_summary else {}
|
|
369
|
+
|
|
370
|
+
# Use the job summary to add the entries for labels with no failures
|
|
371
|
+
# *without* modifying already existing entries.
|
|
372
|
+
failure_summary = {label: states[WmsStates.FAILED] for label, states in report.job_summary.items()}
|
|
373
|
+
for label, count in failure_summary.items():
|
|
374
|
+
if count == 0:
|
|
375
|
+
exit_code_summary.setdefault(label, [])
|
|
376
|
+
|
|
377
|
+
# Check if there are any discrepancies between the data in the exit code
|
|
378
|
+
# summary and the job summary.
|
|
379
|
+
code_summary_labels = set(exit_code_summary)
|
|
380
|
+
failure_summary_labels = set(failure_summary)
|
|
381
|
+
mismatches = {
|
|
382
|
+
label
|
|
383
|
+
for label in failure_summary_labels & code_summary_labels
|
|
384
|
+
if len(exit_code_summary[label]) != failure_summary[label]
|
|
385
|
+
}
|
|
386
|
+
if mismatches:
|
|
387
|
+
warnings.append(
|
|
388
|
+
f"number of exit codes differs from number of failures for job labels: {', '.join(mismatches)}"
|
|
389
|
+
)
|
|
390
|
+
missing = failure_summary_labels - code_summary_labels
|
|
391
|
+
if missing:
|
|
392
|
+
warnings.append(f"exit codes not available for job labels: {', '.join(missing)}")
|
|
393
|
+
|
|
394
|
+
if exit_code_summary:
|
|
395
|
+
report.exit_code_summary = exit_code_summary
|
|
396
|
+
|
|
397
|
+
return warnings
|
|
310
398
|
|
|
311
399
|
|
|
312
400
|
def group_jobs_by_state(jobs):
|
|
@@ -38,12 +38,18 @@ import sys
|
|
|
38
38
|
from collections.abc import Callable, Sequence
|
|
39
39
|
from typing import TextIO
|
|
40
40
|
|
|
41
|
-
from lsst.utils import
|
|
42
|
-
|
|
43
|
-
from .bps_reports import
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
from lsst.utils import doImportType
|
|
42
|
+
|
|
43
|
+
from .bps_reports import (
|
|
44
|
+
DetailedRunReport,
|
|
45
|
+
ExitCodesReport,
|
|
46
|
+
SummaryRunReport,
|
|
47
|
+
compile_code_summary,
|
|
48
|
+
compile_job_summary,
|
|
49
|
+
)
|
|
50
|
+
from .wms_service import BaseWmsService, WmsRunReport, WmsStates
|
|
51
|
+
|
|
52
|
+
BPS_POSTPROCESSORS = (compile_job_summary, compile_code_summary)
|
|
47
53
|
"""Postprocessors for massaging run reports
|
|
48
54
|
(`tuple` [`Callable` [[`WmsRunReport`], None]).
|
|
49
55
|
"""
|
|
@@ -111,7 +117,7 @@ def display_report(
|
|
|
111
117
|
|
|
112
118
|
run_report.add(run, use_global_id=is_global)
|
|
113
119
|
if run_report.message:
|
|
114
|
-
|
|
120
|
+
messages.append(run_report.message)
|
|
115
121
|
|
|
116
122
|
print(run_brief, file=file)
|
|
117
123
|
print("\n", file=file)
|
|
@@ -132,6 +138,8 @@ def display_report(
|
|
|
132
138
|
]
|
|
133
139
|
run_exits_report = ExitCodesReport(fields)
|
|
134
140
|
run_exits_report.add(run, use_global_id=is_global)
|
|
141
|
+
if run_exits_report.message:
|
|
142
|
+
messages.append(run_exits_report.message)
|
|
135
143
|
print("\n", file=file)
|
|
136
144
|
print(run_exits_report, file=file)
|
|
137
145
|
run_exits_report.clear()
|
|
@@ -145,12 +153,13 @@ def display_report(
|
|
|
145
153
|
print(run_brief, file=file)
|
|
146
154
|
|
|
147
155
|
if messages:
|
|
148
|
-
|
|
156
|
+
uniques = list(dict.fromkeys(messages))
|
|
157
|
+
print("\n".join(uniques), file=file)
|
|
149
158
|
print("\n", file=file)
|
|
150
159
|
|
|
151
160
|
|
|
152
161
|
def retrieve_report(
|
|
153
|
-
|
|
162
|
+
wms_service_fqn: str,
|
|
154
163
|
*,
|
|
155
164
|
run_id: str | None = None,
|
|
156
165
|
user: str | None = None,
|
|
@@ -163,7 +172,7 @@ def retrieve_report(
|
|
|
163
172
|
|
|
164
173
|
Parameters
|
|
165
174
|
----------
|
|
166
|
-
|
|
175
|
+
wms_service_fqn : `str`
|
|
167
176
|
Name of the WMS service class.
|
|
168
177
|
run_id : `str`, optional
|
|
169
178
|
A run id the report will be restricted to.
|
|
@@ -196,11 +205,21 @@ def retrieve_report(
|
|
|
196
205
|
messages : `list` [`str`]
|
|
197
206
|
Errors that happened during report retrieval and/or processing.
|
|
198
207
|
Empty if no issues were encountered.
|
|
208
|
+
|
|
209
|
+
Raises
|
|
210
|
+
------
|
|
211
|
+
TypeError
|
|
212
|
+
Raised if the WMS service class is not a subclass of BaseWmsService.
|
|
199
213
|
"""
|
|
200
|
-
messages = []
|
|
214
|
+
messages: list[str] = []
|
|
201
215
|
|
|
202
|
-
wms_service_class =
|
|
216
|
+
wms_service_class = doImportType(wms_service_fqn)
|
|
217
|
+
if not issubclass(wms_service_class, BaseWmsService):
|
|
218
|
+
raise TypeError(
|
|
219
|
+
f"Invalid WMS service class '{wms_service_fqn}'; must be a subclass of BaseWmsService"
|
|
220
|
+
)
|
|
203
221
|
wms_service = wms_service_class({})
|
|
222
|
+
|
|
204
223
|
reports, message = wms_service.report(
|
|
205
224
|
wms_workflow_id=run_id, user=user, hist=hist, pass_thru=pass_thru, is_global=is_global
|
|
206
225
|
)
|
|
@@ -210,12 +229,12 @@ def retrieve_report(
|
|
|
210
229
|
if postprocessors:
|
|
211
230
|
for report in reports:
|
|
212
231
|
for postprocessor in postprocessors:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
if warnings := postprocessor(report):
|
|
233
|
+
for warning in warnings:
|
|
234
|
+
messages.append(
|
|
235
|
+
f"WARNING: Report may be incomplete. "
|
|
236
|
+
f"There was an issue with report postprocessing for '{report.wms_id}': "
|
|
237
|
+
f"{warning} (origin: {postprocessor.__name__})"
|
|
238
|
+
)
|
|
220
239
|
|
|
221
240
|
return reports, messages
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900/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.3900
|
|
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
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/SOURCES.txt
RENAMED
|
@@ -53,6 +53,7 @@ python/lsst_ctrl_bps.egg-info/entry_points.txt
|
|
|
53
53
|
python/lsst_ctrl_bps.egg-info/requires.txt
|
|
54
54
|
python/lsst_ctrl_bps.egg-info/top_level.txt
|
|
55
55
|
python/lsst_ctrl_bps.egg-info/zip-safe
|
|
56
|
+
tests/test_bps_reports.py
|
|
56
57
|
tests/test_bps_utils.py
|
|
57
58
|
tests/test_bpsconfig.py
|
|
58
59
|
tests/test_cli_commands.py
|
|
@@ -42,9 +42,9 @@ from lsst.ctrl.bps import (
|
|
|
42
42
|
WmsJobReport,
|
|
43
43
|
WmsRunReport,
|
|
44
44
|
WmsStates,
|
|
45
|
+
compile_code_summary,
|
|
45
46
|
compile_job_summary,
|
|
46
47
|
)
|
|
47
|
-
from lsst.ctrl.bps.report import retrieve_report
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
class FakeRunReport(BaseRunReport):
|
|
@@ -335,23 +335,30 @@ class ExitCodesReportTestCase(unittest.TestCase):
|
|
|
335
335
|
|
|
336
336
|
self.actual = ExitCodesReport(self.fields)
|
|
337
337
|
|
|
338
|
-
def
|
|
339
|
-
"""Test adding a run
|
|
340
|
-
self.run.jobs = None
|
|
338
|
+
def testAddSuccess(self):
|
|
339
|
+
"""Test adding a run successfully."""
|
|
341
340
|
self.actual.add(self.run)
|
|
341
|
+
|
|
342
|
+
self.assertEqual(len(self.actual), 2)
|
|
342
343
|
self.assertEqual(self.actual, self.expected)
|
|
343
344
|
|
|
344
|
-
def
|
|
345
|
-
"""Test adding a run
|
|
346
|
-
self.run.job_summary =
|
|
345
|
+
def testAddFailure(self):
|
|
346
|
+
"""Test adding a run unsuccessfully."""
|
|
347
|
+
self.run.job_summary = {}
|
|
348
|
+
self.run.exit_code_summary = {}
|
|
349
|
+
|
|
347
350
|
self.actual.add(self.run)
|
|
348
|
-
|
|
351
|
+
|
|
352
|
+
self.assertEqual(len(self.actual), 0)
|
|
353
|
+
self.assertRegex(self.actual.message, r"^WARNING.*report.*incomplete")
|
|
349
354
|
|
|
350
355
|
def testAddWithoutRunSummary(self):
|
|
351
356
|
"""Test adding a run without a run summary."""
|
|
352
357
|
self.run.run_summary = None
|
|
358
|
+
|
|
353
359
|
self.actual.add(self.run)
|
|
354
|
-
|
|
360
|
+
|
|
361
|
+
self.assertRegex(self.actual.message, r"^WARNING.*sorted alphabetically")
|
|
355
362
|
|
|
356
363
|
|
|
357
364
|
class CompileJobSummaryTestCase(unittest.TestCase):
|
|
@@ -364,57 +371,129 @@ class CompileJobSummaryTestCase(unittest.TestCase):
|
|
|
364
371
|
pass
|
|
365
372
|
|
|
366
373
|
def testSummaryExists(self):
|
|
367
|
-
"""Test if existing report is not altered."""
|
|
374
|
+
"""Test if the existing report is not altered."""
|
|
368
375
|
# Create a report with a "fake" job summary, i.e., a summary which
|
|
369
376
|
# differs from the one which would be compiled from the information
|
|
370
377
|
# about individual jobs.
|
|
371
|
-
expected = dataclasses.replace(
|
|
372
|
-
|
|
373
|
-
|
|
378
|
+
expected = dataclasses.replace(
|
|
379
|
+
self.report,
|
|
380
|
+
job_summary={"foo": {state: 1 if state == WmsStates.FAILED else 0 for state in WmsStates}},
|
|
381
|
+
)
|
|
374
382
|
result = dataclasses.replace(expected)
|
|
375
|
-
|
|
383
|
+
|
|
384
|
+
messages = compile_job_summary(result)
|
|
385
|
+
|
|
376
386
|
self.assertEqual(result, expected)
|
|
387
|
+
self.assertFalse(messages)
|
|
377
388
|
|
|
378
389
|
def testSummaryMissing(self):
|
|
379
|
-
"""Test if the summary
|
|
380
|
-
result = dataclasses.replace(self.report)
|
|
381
|
-
|
|
382
|
-
compile_job_summary(result)
|
|
390
|
+
"""Test if the summary is compiled if necessary."""
|
|
391
|
+
result = dataclasses.replace(self.report, job_summary=None)
|
|
392
|
+
|
|
393
|
+
messages = compile_job_summary(result)
|
|
394
|
+
|
|
383
395
|
self.assertEqual(result, self.report)
|
|
396
|
+
self.assertFalse(messages)
|
|
384
397
|
|
|
385
398
|
def testCompilationError(self):
|
|
386
|
-
"""Test if
|
|
387
|
-
self.report
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
399
|
+
"""Test if a warning is issued if the summary cannot be compiled."""
|
|
400
|
+
result = dataclasses.replace(self.report, jobs=None, job_summary=None)
|
|
401
|
+
|
|
402
|
+
messages = compile_job_summary(result)
|
|
403
|
+
|
|
404
|
+
self.assertEqual(len(messages), 1)
|
|
405
|
+
self.assertRegex(messages[0], r"information.*not available")
|
|
391
406
|
|
|
392
407
|
|
|
393
|
-
class
|
|
394
|
-
"""Test
|
|
408
|
+
class CompileCodeSummaryTestCase(unittest.TestCase):
|
|
409
|
+
"""Test compiling a code summary."""
|
|
395
410
|
|
|
396
411
|
def setUp(self):
|
|
397
|
-
self.report =
|
|
412
|
+
self.report = WmsRunReport(
|
|
413
|
+
wms_id="1.0",
|
|
414
|
+
global_wms_id="foo#1.0",
|
|
415
|
+
path="/path/to/run",
|
|
416
|
+
label="label",
|
|
417
|
+
run="run",
|
|
418
|
+
project="dev",
|
|
419
|
+
campaign="testing",
|
|
420
|
+
payload="test",
|
|
421
|
+
operator="tester",
|
|
422
|
+
run_summary="foo:1;bar:1;baz:1",
|
|
423
|
+
state=WmsStates.RUNNING,
|
|
424
|
+
jobs=[
|
|
425
|
+
WmsJobReport(wms_id="1.0", name="", label="foo", state=WmsStates.SUCCEEDED),
|
|
426
|
+
WmsJobReport(wms_id="2.0", name="", label="bar", state=WmsStates.FAILED),
|
|
427
|
+
WmsJobReport(wms_id="3.0", name="", label="baz", state=WmsStates.RUNNING),
|
|
428
|
+
],
|
|
429
|
+
total_number_jobs=3,
|
|
430
|
+
job_state_counts={
|
|
431
|
+
state: 1 if state in {WmsStates.SUCCEEDED, WmsStates.FAILED, WmsStates.RUNNING} else 0
|
|
432
|
+
for state in WmsStates
|
|
433
|
+
},
|
|
434
|
+
job_summary={
|
|
435
|
+
"foo": {state: 1 if state == WmsStates.SUCCEEDED else 0 for state in WmsStates},
|
|
436
|
+
"bar": {state: 1 if state == WmsStates.FAILED else 0 for state in WmsStates},
|
|
437
|
+
"baz": {state: 1 if state == WmsStates.RUNNING else 0 for state in WmsStates},
|
|
438
|
+
},
|
|
439
|
+
exit_code_summary={"foo": [], "bar": [1], "baz": []},
|
|
440
|
+
)
|
|
398
441
|
|
|
399
442
|
def tearDown(self):
|
|
400
443
|
pass
|
|
401
444
|
|
|
402
|
-
def
|
|
403
|
-
"""Test
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
self.assertEqual(
|
|
445
|
+
def testAddingMissingEntries(self):
|
|
446
|
+
"""Test if the missing entries are added to the summary."""
|
|
447
|
+
result = dataclasses.replace(self.report, exit_code_summary={"bar": [1]})
|
|
448
|
+
|
|
449
|
+
messages = compile_code_summary(result)
|
|
450
|
+
|
|
451
|
+
self.assertEqual(result, self.report)
|
|
409
452
|
self.assertFalse(messages)
|
|
410
453
|
|
|
411
|
-
def
|
|
412
|
-
"""Test
|
|
413
|
-
report,
|
|
414
|
-
|
|
415
|
-
|
|
454
|
+
def testDetectingMismatches(self):
|
|
455
|
+
"""Test if a mismatch between exit codes and failures is reported."""
|
|
456
|
+
expected = dataclasses.replace(self.report, exit_code_summary={"foo": [1], "bar": [1], "baz": []})
|
|
457
|
+
result = dataclasses.replace(expected)
|
|
458
|
+
|
|
459
|
+
messages = compile_code_summary(result)
|
|
460
|
+
|
|
461
|
+
self.assertEqual(result, expected)
|
|
416
462
|
self.assertEqual(len(messages), 1)
|
|
417
|
-
self.assertRegex(messages[0], "
|
|
463
|
+
self.assertRegex(messages[0], r"exit codes.*differs.*failures.*labels: foo")
|
|
464
|
+
|
|
465
|
+
def testDetectingOmissions(self):
|
|
466
|
+
"""Test if a failure not reflected in exit codes is reported."""
|
|
467
|
+
expected = dataclasses.replace(self.report, exit_code_summary={"foo": [], "baz": []})
|
|
468
|
+
result = dataclasses.replace(expected)
|
|
469
|
+
|
|
470
|
+
messages = compile_code_summary(result)
|
|
471
|
+
|
|
472
|
+
self.assertEqual(result, expected)
|
|
473
|
+
self.assertEqual(len(messages), 1)
|
|
474
|
+
self.assertRegex(messages[0], r"exit codes.*not available.*labels: bar")
|
|
475
|
+
|
|
476
|
+
def testDetectingDiscrepancies(self):
|
|
477
|
+
"""Test if multiple discrepancies are reported."""
|
|
478
|
+
expected = dataclasses.replace(self.report, exit_code_summary={"foo": [], "baz": [1]})
|
|
479
|
+
result = dataclasses.replace(expected)
|
|
480
|
+
|
|
481
|
+
messages = compile_code_summary(result)
|
|
482
|
+
|
|
483
|
+
self.assertEqual(result, expected)
|
|
484
|
+
self.assertEqual(len(messages), 2)
|
|
485
|
+
self.assertRegex(messages[0], r"exit codes.*differs.*failures.*labels: baz")
|
|
486
|
+
self.assertRegex(messages[1], r"exit codes.*not available.*labels: bar")
|
|
487
|
+
|
|
488
|
+
def testHandlingNoJobSummary(self):
|
|
489
|
+
"""Test if the existing report is not altered if no job summary."""
|
|
490
|
+
expected = dataclasses.replace(self.report, job_summary=None)
|
|
491
|
+
result = dataclasses.replace(expected)
|
|
492
|
+
|
|
493
|
+
messages = compile_code_summary(result)
|
|
494
|
+
|
|
495
|
+
self.assertEqual(result, expected)
|
|
496
|
+
self.assertFalse(messages)
|
|
418
497
|
|
|
419
498
|
|
|
420
499
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
"""Tests for reporting mechanism."""
|
|
29
|
+
|
|
30
|
+
import dataclasses
|
|
31
|
+
import unittest
|
|
32
|
+
|
|
33
|
+
from wms_test_utils import TEST_REPORT
|
|
34
|
+
|
|
35
|
+
from lsst.ctrl.bps import compile_job_summary
|
|
36
|
+
from lsst.ctrl.bps.report import retrieve_report
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RetrieveReportTestCase(unittest.TestCase):
|
|
40
|
+
"""Test report retrieval."""
|
|
41
|
+
|
|
42
|
+
def setUp(self):
|
|
43
|
+
self.report = dataclasses.replace(TEST_REPORT)
|
|
44
|
+
|
|
45
|
+
def tearDown(self):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def testRetrievalPostprocessingSuccessful(self):
|
|
49
|
+
"""Test retrieving a report successfully."""
|
|
50
|
+
reports, messages = retrieve_report(
|
|
51
|
+
"wms_test_utils.WmsServiceSuccess", run_id="1.0", postprocessors=(compile_job_summary,)
|
|
52
|
+
)
|
|
53
|
+
self.assertEqual(len(reports), 1)
|
|
54
|
+
self.assertEqual(reports[0], self.report)
|
|
55
|
+
self.assertFalse(messages)
|
|
56
|
+
|
|
57
|
+
def testRetrievalPostprocessingFailed(self):
|
|
58
|
+
"""Test failing to retrieve a report."""
|
|
59
|
+
report, messages = retrieve_report(
|
|
60
|
+
"wms_test_utils.WmsServiceFailure", postprocessors=(compile_job_summary,)
|
|
61
|
+
)
|
|
62
|
+
self.assertEqual(len(messages), 1)
|
|
63
|
+
self.assertRegex(messages[0], "issue.*postprocessing")
|
|
64
|
+
|
|
65
|
+
def testRetrievalInvalidClass(self):
|
|
66
|
+
"""Test retrieving a report with an invalid class."""
|
|
67
|
+
with self.assertRaises(TypeError):
|
|
68
|
+
retrieve_report("wms_test_utils.WmsServiceInvalid", run_id="1.0", postprocessors=None)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
unittest.main()
|
|
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
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/_exceptions.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/bps_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/cmd/__init__.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/cmd/commands.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/__init__.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/arguments.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/cli/opt/options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/etc/bps_defaults.yaml
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/generic_workflow.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/initialize.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/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.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst/ctrl/bps/wms_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/python/lsst_ctrl_bps.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/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.3700 → lsst_ctrl_bps-29.2025.3900}/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
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.3700 → lsst_ctrl_bps-29.2025.3900}/tests/test_quantum_clustering_funcs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|