lsst-ctrl-bps 29.2025.3700__py3-none-any.whl → 29.2025.3900__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,7 +27,14 @@
27
27
 
28
28
  """Classes and functions used in reporting run status."""
29
29
 
30
- __all__ = ["BaseRunReport", "DetailedRunReport", "ExitCodesReport", "SummaryRunReport", "compile_job_summary"]
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 all(self._table == other._table)
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 maybe incomplete."
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
- # Use label ordering from the run summary as it should reflect
238
- # the ordering of the pipetasks in the pipeline.
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
- else:
245
- id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
246
- self._msg = f"WARNING: Job summary for run '{id_}' not available, report maybe incomplete."
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
- exit_code_summary = run_report.exit_code_summary
268
+ missing_labels = set()
255
269
  for label in labels:
256
- exit_codes = exit_code_summary[label]
257
-
258
- pyld_errors = [code for code in exit_codes if code in pyld_error_codes]
259
- pyld_error_count = len(pyld_errors)
260
- pyld_error_summary = (
261
- ", ".join(sorted(str(code) for code in set(pyld_errors))) if pyld_errors else "None"
262
- )
263
-
264
- infra_errors = [code for code in exit_codes if code not in pyld_error_codes]
265
- infra_error_count = len(infra_errors)
266
- infra_error_summary = (
267
- ", ".join(sorted(str(code) for code in set(infra_errors))) if infra_errors else "None"
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
- run = [label, pyld_error_count, pyld_error_summary, infra_error_count, infra_error_summary]
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) -> None:
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
- Raises
293
- ------
294
- ValueError
295
- Raised if the job summary *and* information about individual jobs
296
- is not available.
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
- if not report.jobs:
301
- raise ValueError("job summary cannot be compiled: information about individual jobs not available.")
302
- job_summary = {}
303
- by_label = group_jobs_by_label(report.jobs)
304
- for label, job_group in by_label.items():
305
- by_label_state = group_jobs_by_state(job_group)
306
- _LOG.debug("by_label_state = %s", by_label_state)
307
- counts = {state: len(jobs) for state, jobs in by_label_state.items()}
308
- job_summary[label] = counts
309
- report.job_summary = job_summary
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):
lsst/ctrl/bps/report.py CHANGED
@@ -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 doImport
42
-
43
- from .bps_reports import DetailedRunReport, ExitCodesReport, SummaryRunReport, compile_job_summary
44
- from .wms_service import WmsRunReport, WmsStates
45
-
46
- BPS_POSTPROCESSORS = (compile_job_summary,)
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
- print(run_report.message, file=file)
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
- print("\n".join(messages), file=file)
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
- wms_service: str,
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
- wms_service : `str`
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 = doImport(wms_service)
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
- try:
214
- postprocessor(report)
215
- except Exception as exc:
216
- messages.append(
217
- f"Postprocessing error for '{report.wms_id}': {str(exc)} "
218
- f"(origin: {postprocessor.__name__})"
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/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "29.2025.3700"
2
+ __version__ = "29.2025.3900"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lsst-ctrl-bps
3
- Version: 29.2025.3700
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
@@ -4,7 +4,7 @@ lsst/ctrl/bps/__init__.py,sha256=qNei3h4EwqUnyyCsBUkYXcRNNlSvzUzbM6Zdkd4eklE,166
4
4
  lsst/ctrl/bps/_exceptions.py,sha256=qNftFVE8yhiRD8yvACKEf5iHhi1o2eaa69ZCBvw6lDo,1974
5
5
  lsst/ctrl/bps/bps_config.py,sha256=4t1jyIBiw9leAySjkcksCaZ3CPJMOt8afxGxTEWRSyQ,18460
6
6
  lsst/ctrl/bps/bps_draw.py,sha256=Sl04jpbm6jB_5lBzfJprqnDes9t_aqQQhr0Ag5RSv-Y,1984
7
- lsst/ctrl/bps/bps_reports.py,sha256=j6HdAvkOBTSxSHmkx_vd04FvifToTaZVGpwfF-LIUu0,12487
7
+ lsst/ctrl/bps/bps_reports.py,sha256=73l5Gz00VQq8NgoBrGkvCaUQJE5kw2emhQxxzthMQzs,15574
8
8
  lsst/ctrl/bps/bps_utils.py,sha256=-w0HSUXKw8jgJFl28yzwJuuqLqT_UOw12_XQBELUWrE,12206
9
9
  lsst/ctrl/bps/cancel.py,sha256=mAdBi-oUpepyo-1MCqx_I34dbm6cqT0VJu3d2-y9T2Y,3317
10
10
  lsst/ctrl/bps/clustered_quantum_graph.py,sha256=mBf8s_DlTzGCFq7aAKmD1cAXq6Cqr8QxjwpgXPOQcRc,18765
@@ -17,12 +17,12 @@ lsst/ctrl/bps/ping.py,sha256=orwTZUNFtlexMYFcNWW_48jaa7Jo1oK4_eb_HuC-p5E,2235
17
17
  lsst/ctrl/bps/pre_transform.py,sha256=mPUW1QuHwgkSgZGVK6xO4RN590z4g1zf3p4kNsx_yOg,10222
18
18
  lsst/ctrl/bps/prepare.py,sha256=Fa2OEQIo4Pa8R5WmRo0PvJgXWNjynRijATvu1x80qlw,3129
19
19
  lsst/ctrl/bps/quantum_clustering_funcs.py,sha256=8fidDw53MpcqeWVXs0F0jIvSU0fLV_-1fEbC-Jhqg28,31816
20
- lsst/ctrl/bps/report.py,sha256=6qCt6-0apuVZrevtQzJLRmQc_ve_uPyNH3HkgC5KSjc,7898
20
+ lsst/ctrl/bps/report.py,sha256=893m2KBBaQouAml8DKmmVxBo-Q7fFizxl_OT6wyFBXk,8575
21
21
  lsst/ctrl/bps/restart.py,sha256=yVwxeviLiehyIfPmwU-H3tJ9ou7OWZZcrNf8PMxjr8o,2298
22
22
  lsst/ctrl/bps/status.py,sha256=Lrt0cAqROv77B8UvYXGimCa4cDHBD1N0K2Xx7oS6fXk,3362
23
23
  lsst/ctrl/bps/submit.py,sha256=Ev-yhcoZwtBPIo5bRt_4XFJRtgQBd8JHUurEfn01HpU,2880
24
24
  lsst/ctrl/bps/transform.py,sha256=P3dGwKStoCLOn7F2Oez6B__f7bGV5jlUF_Vj9lJQcU0,35256
25
- lsst/ctrl/bps/version.py,sha256=AQOxvPBwPILItheZOADktYgvye1bfruQzhiAhgZby0A,55
25
+ lsst/ctrl/bps/version.py,sha256=qaaoasFkyU8Kx3tKT5jPh3H-bKD5Y8pAmsL3Scaq2UU,55
26
26
  lsst/ctrl/bps/wms_service.py,sha256=l3T6i1MG72dhHY0KXMUlBjWUpCLOfaySs7o2W8oCwhs,18891
27
27
  lsst/ctrl/bps/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  lsst/ctrl/bps/cli/bps.py,sha256=CHfAL-U4mSi-FqeGKtmkX5nI5H9gkRre4XEWNVdeMRk,2559
@@ -35,13 +35,13 @@ lsst/ctrl/bps/cli/opt/options.py,sha256=pZQpjJ2Vrx6kYJGs5vVERBMFstPocwBXHffxkWkm
35
35
  lsst/ctrl/bps/etc/bps_defaults.yaml,sha256=UwKAKWl09-Xjtl6kiz-AiXA9SDczeCvPGRXOPG3fzb8,4828
36
36
  lsst/ctrl/bps/tests/config_test_utils.py,sha256=WM8Vrigk4OO0TBoL1A73a6hLhf2a6-ACD20fROJ0U7A,3537
37
37
  lsst/ctrl/bps/tests/gw_test_utils.py,sha256=zVVQqzwSiQgPgk9TnqDzgR7uDnaTMeuBLYKA8vOp5RI,22452
38
- lsst_ctrl_bps-29.2025.3700.dist-info/licenses/COPYRIGHT,sha256=Lc6NoAEFQ65v_SmtS9NwfHTOuSUtC2Umbjv5zyowiQM,61
39
- lsst_ctrl_bps-29.2025.3700.dist-info/licenses/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
40
- lsst_ctrl_bps-29.2025.3700.dist-info/licenses/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
41
- lsst_ctrl_bps-29.2025.3700.dist-info/licenses/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
42
- lsst_ctrl_bps-29.2025.3700.dist-info/METADATA,sha256=1aCeZeV9asx8ORK4RKR_L-7BNEHXwJwsgq4Bd26750U,2190
43
- lsst_ctrl_bps-29.2025.3700.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
- lsst_ctrl_bps-29.2025.3700.dist-info/entry_points.txt,sha256=d6FhN79h7s9frdBI7YkScsGEInwpGGub49pAjXWbIbI,51
45
- lsst_ctrl_bps-29.2025.3700.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
46
- lsst_ctrl_bps-29.2025.3700.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
47
- lsst_ctrl_bps-29.2025.3700.dist-info/RECORD,,
38
+ lsst_ctrl_bps-29.2025.3900.dist-info/licenses/COPYRIGHT,sha256=Lc6NoAEFQ65v_SmtS9NwfHTOuSUtC2Umbjv5zyowiQM,61
39
+ lsst_ctrl_bps-29.2025.3900.dist-info/licenses/LICENSE,sha256=pRExkS03v0MQW-neNfIcaSL6aiAnoLxYgtZoFzQ6zkM,232
40
+ lsst_ctrl_bps-29.2025.3900.dist-info/licenses/bsd_license.txt,sha256=7MIcv8QRX9guUtqPSBDMPz2SnZ5swI-xZMqm_VDSfxY,1606
41
+ lsst_ctrl_bps-29.2025.3900.dist-info/licenses/gpl-v3.0.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
42
+ lsst_ctrl_bps-29.2025.3900.dist-info/METADATA,sha256=Etf24NWJ8pPnq6jmRV-0n55ZhvJ29kDoSXiYFsRHzso,2190
43
+ lsst_ctrl_bps-29.2025.3900.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ lsst_ctrl_bps-29.2025.3900.dist-info/entry_points.txt,sha256=d6FhN79h7s9frdBI7YkScsGEInwpGGub49pAjXWbIbI,51
45
+ lsst_ctrl_bps-29.2025.3900.dist-info/top_level.txt,sha256=eUWiOuVVm9wwTrnAgiJT6tp6HQHXxIhj2QSZ7NYZH80,5
46
+ lsst_ctrl_bps-29.2025.3900.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
47
+ lsst_ctrl_bps-29.2025.3900.dist-info/RECORD,,