lsst-pipe-base 29.2025.4600__py3-none-any.whl → 29.2025.4800__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.
- lsst/pipe/base/connections.py +11 -0
- lsst/pipe/base/quantum_graph/_common.py +15 -1
- lsst/pipe/base/quantum_graph/_multiblock.py +14 -39
- lsst/pipe/base/quantum_graph/_predicted.py +77 -73
- lsst/pipe/base/quantum_graph/_provenance.py +73 -144
- lsst/pipe/base/quantum_graph/aggregator/_communicators.py +10 -10
- lsst/pipe/base/quantum_graph/aggregator/_scanner.py +88 -60
- lsst/pipe/base/quantum_graph/aggregator/_structs.py +36 -19
- lsst/pipe/base/quantum_graph/aggregator/_supervisor.py +7 -10
- lsst/pipe/base/quantum_graph/aggregator/_writer.py +55 -144
- lsst/pipe/base/quantum_graph_builder.py +0 -1
- lsst/pipe/base/version.py +1 -1
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/METADATA +1 -1
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/RECORD +22 -22
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/WHEEL +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/entry_points.txt +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/COPYRIGHT +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/LICENSE +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/bsd_license.txt +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/gpl-v3.0.txt +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/top_level.txt +0 -0
- {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/zip-safe +0 -0
|
@@ -50,9 +50,9 @@ from .._predicted import (
|
|
|
50
50
|
PredictedQuantumDatasetsModel,
|
|
51
51
|
PredictedQuantumGraphReader,
|
|
52
52
|
)
|
|
53
|
-
from .._provenance import ProvenanceQuantumAttemptModel
|
|
53
|
+
from .._provenance import ProvenanceInitQuantumModel, ProvenanceQuantumAttemptModel, ProvenanceQuantumModel
|
|
54
54
|
from ._communicators import ScannerCommunicator
|
|
55
|
-
from ._structs import IngestRequest, ScanReport,
|
|
55
|
+
from ._structs import IngestRequest, InProgressScan, ScanReport, ScanStatus, WriteRequest
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
@dataclasses.dataclass
|
|
@@ -185,7 +185,7 @@ class Scanner:
|
|
|
185
185
|
ref = self.reader.components.make_dataset_ref(predicted)
|
|
186
186
|
return self.qbb.stored(ref)
|
|
187
187
|
|
|
188
|
-
def scan_quantum(self, quantum_id: uuid.UUID) ->
|
|
188
|
+
def scan_quantum(self, quantum_id: uuid.UUID) -> InProgressScan:
|
|
189
189
|
"""Scan for a quantum's completion and error status, and its output
|
|
190
190
|
datasets' existence.
|
|
191
191
|
|
|
@@ -196,11 +196,11 @@ class Scanner:
|
|
|
196
196
|
|
|
197
197
|
Returns
|
|
198
198
|
-------
|
|
199
|
-
result : `
|
|
199
|
+
result : `InProgressScan`
|
|
200
200
|
Scan result struct.
|
|
201
201
|
"""
|
|
202
202
|
if (predicted_quantum := self.init_quanta.get(quantum_id)) is not None:
|
|
203
|
-
result =
|
|
203
|
+
result = InProgressScan(predicted_quantum.quantum_id, status=ScanStatus.INIT)
|
|
204
204
|
self.comms.log.debug("Created init scan for %s (%s)", quantum_id, predicted_quantum.task_label)
|
|
205
205
|
else:
|
|
206
206
|
self.reader.read_quantum_datasets([quantum_id])
|
|
@@ -211,7 +211,7 @@ class Scanner:
|
|
|
211
211
|
predicted_quantum.task_label,
|
|
212
212
|
predicted_quantum.data_coordinate,
|
|
213
213
|
)
|
|
214
|
-
result =
|
|
214
|
+
result = InProgressScan(predicted_quantum.quantum_id, ScanStatus.INCOMPLETE)
|
|
215
215
|
del self.reader.components.quantum_datasets[quantum_id]
|
|
216
216
|
last_attempt = ProvenanceQuantumAttemptModel()
|
|
217
217
|
if not self._read_log(predicted_quantum, result, last_attempt):
|
|
@@ -230,14 +230,12 @@ class Scanner:
|
|
|
230
230
|
result.attempts.append(last_attempt)
|
|
231
231
|
assert result.status is not ScanStatus.INCOMPLETE
|
|
232
232
|
assert result.status is not ScanStatus.ABANDONED
|
|
233
|
-
assert result.log_model is not None, "Only set to None after converting to JSON."
|
|
234
|
-
assert result.metadata_model is not None, "Only set to None after converting to JSON."
|
|
235
233
|
|
|
236
|
-
if len(result.
|
|
234
|
+
if len(result.logs.attempts) < len(result.attempts):
|
|
237
235
|
# Logs were not found for this attempt; must have been a hard error
|
|
238
236
|
# that kept the `finally` block from running or otherwise
|
|
239
237
|
# interrupted the writing of the logs.
|
|
240
|
-
result.
|
|
238
|
+
result.logs.attempts.append(None)
|
|
241
239
|
if result.status is ScanStatus.SUCCESSFUL:
|
|
242
240
|
# But we found the metadata! Either that hard error happened
|
|
243
241
|
# at a very unlucky time (in between those two writes), or
|
|
@@ -245,50 +243,37 @@ class Scanner:
|
|
|
245
243
|
result.attempts[-1].status = QuantumAttemptStatus.LOGS_MISSING
|
|
246
244
|
else:
|
|
247
245
|
result.attempts[-1].status = QuantumAttemptStatus.FAILED
|
|
248
|
-
if len(result.
|
|
246
|
+
if len(result.metadata.attempts) < len(result.attempts):
|
|
249
247
|
# Metadata missing usually just means a failure. In any case, the
|
|
250
248
|
# status will already be correct, either because it was set to a
|
|
251
249
|
# failure when we read the logs, or left at UNKNOWN if there were
|
|
252
250
|
# no logs. Note that scanners never process BLOCKED quanta at all.
|
|
253
|
-
result.
|
|
254
|
-
assert len(result.
|
|
255
|
-
result.
|
|
256
|
-
)
|
|
251
|
+
result.metadata.attempts.append(None)
|
|
252
|
+
assert len(result.logs.attempts) == len(result.attempts) or len(result.metadata.attempts) == len(
|
|
253
|
+
result.attempts
|
|
254
|
+
), (
|
|
257
255
|
"The only way we can add more than one quantum attempt is by "
|
|
258
256
|
"extracting info stored with the logs, and that always appends "
|
|
259
257
|
"a log attempt and a metadata attempt, so this must be a bug in "
|
|
260
258
|
"the scanner."
|
|
261
259
|
)
|
|
262
|
-
# Now that we're done gathering the log and metadata information into
|
|
263
|
-
# models, dump them to JSON and delete the originals.
|
|
264
|
-
result.log_content = result.log_model.model_dump_json().encode()
|
|
265
|
-
result.log_model = None
|
|
266
|
-
result.metadata_content = result.metadata_model.model_dump_json().encode()
|
|
267
|
-
result.metadata_model = None
|
|
268
|
-
if self.compressor is not None:
|
|
269
|
-
if result.log_content is not None:
|
|
270
|
-
result.log_content = self.compressor.compress(result.log_content)
|
|
271
|
-
if result.metadata_content is not None:
|
|
272
|
-
result.metadata_content = self.compressor.compress(result.metadata_content)
|
|
273
|
-
result.is_compressed = True
|
|
274
260
|
# Scan for output dataset existence, skipping any the metadata reported
|
|
275
|
-
#
|
|
276
|
-
#
|
|
261
|
+
# on as well as and the metadata and logs themselves (since we just
|
|
262
|
+
# checked those).
|
|
277
263
|
for predicted_output in itertools.chain.from_iterable(predicted_quantum.outputs.values()):
|
|
278
|
-
if predicted_output.dataset_id not in result.
|
|
279
|
-
predicted_output
|
|
280
|
-
):
|
|
281
|
-
result.existing_outputs.add(predicted_output.dataset_id)
|
|
264
|
+
if predicted_output.dataset_id not in result.outputs:
|
|
265
|
+
result.outputs[predicted_output.dataset_id] = self.scan_dataset(predicted_output)
|
|
282
266
|
to_ingest = self._make_ingest_request(predicted_quantum, result)
|
|
283
|
-
self.comms.report_scan(ScanReport(result.quantum_id, result.status))
|
|
284
267
|
if self.comms.config.output_path is not None:
|
|
285
|
-
self.
|
|
268
|
+
to_write = self._make_write_request(predicted_quantum, result)
|
|
269
|
+
self.comms.request_write(to_write)
|
|
286
270
|
self.comms.request_ingest(to_ingest)
|
|
271
|
+
self.comms.report_scan(ScanReport(result.quantum_id, result.status))
|
|
287
272
|
self.comms.log.debug("Finished scan for %s.", quantum_id)
|
|
288
273
|
return result
|
|
289
274
|
|
|
290
275
|
def _make_ingest_request(
|
|
291
|
-
self, predicted_quantum: PredictedQuantumDatasetsModel, result:
|
|
276
|
+
self, predicted_quantum: PredictedQuantumDatasetsModel, result: InProgressScan
|
|
292
277
|
) -> IngestRequest:
|
|
293
278
|
"""Make an ingest request from a quantum scan.
|
|
294
279
|
|
|
@@ -296,7 +281,7 @@ class Scanner:
|
|
|
296
281
|
----------
|
|
297
282
|
predicted_quantum : `PredictedQuantumDatasetsModel`
|
|
298
283
|
Information about the predicted quantum.
|
|
299
|
-
result : `
|
|
284
|
+
result : `InProgressScan`
|
|
300
285
|
Result of a quantum scan.
|
|
301
286
|
|
|
302
287
|
Returns
|
|
@@ -309,17 +294,58 @@ class Scanner:
|
|
|
309
294
|
}
|
|
310
295
|
to_ingest_predicted: list[PredictedDatasetModel] = []
|
|
311
296
|
to_ingest_refs: list[DatasetRef] = []
|
|
312
|
-
for dataset_id in result.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
297
|
+
for dataset_id, was_produced in result.outputs.items():
|
|
298
|
+
if was_produced:
|
|
299
|
+
predicted_output = predicted_outputs_by_id[dataset_id]
|
|
300
|
+
to_ingest_predicted.append(predicted_output)
|
|
301
|
+
to_ingest_refs.append(self.reader.components.make_dataset_ref(predicted_output))
|
|
316
302
|
to_ingest_records = self.qbb._datastore.export_predicted_records(to_ingest_refs)
|
|
317
303
|
return IngestRequest(result.quantum_id, to_ingest_predicted, to_ingest_records)
|
|
318
304
|
|
|
305
|
+
def _make_write_request(
|
|
306
|
+
self, predicted_quantum: PredictedQuantumDatasetsModel, result: InProgressScan
|
|
307
|
+
) -> WriteRequest:
|
|
308
|
+
"""Make a write request from a quantum scan.
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
predicted_quantum : `PredictedQuantumDatasetsModel`
|
|
313
|
+
Information about the predicted quantum.
|
|
314
|
+
result : `InProgressScan`
|
|
315
|
+
Result of a quantum scan.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
write_request : `WriteRequest`
|
|
320
|
+
A request to be sent to the writer.
|
|
321
|
+
"""
|
|
322
|
+
quantum: ProvenanceInitQuantumModel | ProvenanceQuantumModel
|
|
323
|
+
if result.status is ScanStatus.INIT:
|
|
324
|
+
quantum = ProvenanceInitQuantumModel.from_predicted(predicted_quantum)
|
|
325
|
+
else:
|
|
326
|
+
quantum = ProvenanceQuantumModel.from_predicted(predicted_quantum)
|
|
327
|
+
quantum.attempts = result.attempts
|
|
328
|
+
request = WriteRequest(
|
|
329
|
+
result.quantum_id,
|
|
330
|
+
result.status,
|
|
331
|
+
existing_outputs={
|
|
332
|
+
dataset_id for dataset_id, was_produced in result.outputs.items() if was_produced
|
|
333
|
+
},
|
|
334
|
+
quantum=quantum.model_dump_json().encode(),
|
|
335
|
+
logs=result.logs.model_dump_json().encode() if result.logs.attempts else b"",
|
|
336
|
+
metadata=result.metadata.model_dump_json().encode() if result.metadata.attempts else b"",
|
|
337
|
+
)
|
|
338
|
+
if self.compressor is not None:
|
|
339
|
+
request.quantum = self.compressor.compress(request.quantum)
|
|
340
|
+
request.logs = self.compressor.compress(request.logs) if request.logs else b""
|
|
341
|
+
request.metadata = self.compressor.compress(request.metadata) if request.metadata else b""
|
|
342
|
+
request.is_compressed = True
|
|
343
|
+
return request
|
|
344
|
+
|
|
319
345
|
def _read_metadata(
|
|
320
346
|
self,
|
|
321
347
|
predicted_quantum: PredictedQuantumDatasetsModel,
|
|
322
|
-
result:
|
|
348
|
+
result: InProgressScan,
|
|
323
349
|
last_attempt: ProvenanceQuantumAttemptModel,
|
|
324
350
|
) -> bool:
|
|
325
351
|
"""Attempt to read the metadata dataset for a quantum to extract
|
|
@@ -329,7 +355,7 @@ class Scanner:
|
|
|
329
355
|
----------
|
|
330
356
|
predicted_quantum : `PredictedQuantumDatasetsModel`
|
|
331
357
|
Information about the predicted quantum.
|
|
332
|
-
result : `
|
|
358
|
+
result : `InProgressScan`
|
|
333
359
|
Result object to be modified in-place.
|
|
334
360
|
last_attempt : `ScanningProvenanceQuantumAttemptModel`
|
|
335
361
|
Structure to fill in with information about the last attempt to
|
|
@@ -348,6 +374,7 @@ class Scanner:
|
|
|
348
374
|
# here.
|
|
349
375
|
metadata: TaskMetadata = self.qbb.get(ref, storageClass="TaskMetadata")
|
|
350
376
|
except FileNotFoundError:
|
|
377
|
+
result.outputs[ref.id] = False
|
|
351
378
|
if self.comms.config.assume_complete:
|
|
352
379
|
result.status = ScanStatus.FAILED
|
|
353
380
|
else:
|
|
@@ -355,7 +382,7 @@ class Scanner:
|
|
|
355
382
|
return False
|
|
356
383
|
else:
|
|
357
384
|
result.status = ScanStatus.SUCCESSFUL
|
|
358
|
-
result.
|
|
385
|
+
result.outputs[ref.id] = True
|
|
359
386
|
last_attempt.status = QuantumAttemptStatus.SUCCESSFUL
|
|
360
387
|
try:
|
|
361
388
|
# Int conversion guards against spurious conversion to
|
|
@@ -371,20 +398,23 @@ class Scanner:
|
|
|
371
398
|
except LookupError:
|
|
372
399
|
pass
|
|
373
400
|
try:
|
|
374
|
-
|
|
375
|
-
uuid.UUID(id_str)
|
|
376
|
-
)
|
|
401
|
+
for id_str in ensure_iterable(metadata["quantum"].getArray("outputs")):
|
|
402
|
+
result.outputs[uuid.UUID(id_str)]
|
|
377
403
|
except LookupError:
|
|
378
404
|
pass
|
|
405
|
+
else:
|
|
406
|
+
# If the metadata told us what it wrote, anything not in that
|
|
407
|
+
# list was not written.
|
|
408
|
+
for predicted_output in itertools.chain.from_iterable(predicted_quantum.outputs.values()):
|
|
409
|
+
result.outputs.setdefault(predicted_output.dataset_id, False)
|
|
379
410
|
last_attempt.resource_usage = QuantumResourceUsage.from_task_metadata(metadata)
|
|
380
|
-
|
|
381
|
-
result.metadata_model.attempts.append(metadata)
|
|
411
|
+
result.metadata.attempts.append(metadata)
|
|
382
412
|
return True
|
|
383
413
|
|
|
384
414
|
def _read_log(
|
|
385
415
|
self,
|
|
386
416
|
predicted_quantum: PredictedQuantumDatasetsModel,
|
|
387
|
-
result:
|
|
417
|
+
result: InProgressScan,
|
|
388
418
|
last_attempt: ProvenanceQuantumAttemptModel,
|
|
389
419
|
) -> bool:
|
|
390
420
|
"""Attempt to read the log dataset for a quantum to test for the
|
|
@@ -395,7 +425,7 @@ class Scanner:
|
|
|
395
425
|
----------
|
|
396
426
|
predicted_quantum : `PredictedQuantumDatasetsModel`
|
|
397
427
|
Information about the predicted quantum.
|
|
398
|
-
result : `
|
|
428
|
+
result : `InProgressScan`
|
|
399
429
|
Result object to be modified in-place.
|
|
400
430
|
last_attempt : `ScanningProvenanceQuantumAttemptModel`
|
|
401
431
|
Structure to fill in with information about the last attempt to
|
|
@@ -413,6 +443,7 @@ class Scanner:
|
|
|
413
443
|
# If it's not we'll probably get pydantic validation errors here.
|
|
414
444
|
log_records: ButlerLogRecords = self.qbb.get(ref)
|
|
415
445
|
except FileNotFoundError:
|
|
446
|
+
result.outputs[ref.id] = False
|
|
416
447
|
if self.comms.config.assume_complete:
|
|
417
448
|
result.status = ScanStatus.FAILED
|
|
418
449
|
else:
|
|
@@ -424,18 +455,17 @@ class Scanner:
|
|
|
424
455
|
# the logs exist. This will usually get replaced by SUCCESSFUL
|
|
425
456
|
# when we look for metadata next.
|
|
426
457
|
last_attempt.status = QuantumAttemptStatus.FAILED
|
|
427
|
-
result.
|
|
458
|
+
result.outputs[ref.id] = True
|
|
428
459
|
if log_records.extra:
|
|
429
460
|
log_extra = _ExecutionLogRecordsExtra.model_validate(log_records.extra)
|
|
430
461
|
self._extract_from_log_extra(log_extra, result, last_attempt=last_attempt)
|
|
431
|
-
|
|
432
|
-
result.log_model.attempts.append(list(log_records))
|
|
462
|
+
result.logs.attempts.append(list(log_records))
|
|
433
463
|
return True
|
|
434
464
|
|
|
435
465
|
def _extract_from_log_extra(
|
|
436
466
|
self,
|
|
437
467
|
log_extra: _ExecutionLogRecordsExtra,
|
|
438
|
-
result:
|
|
468
|
+
result: InProgressScan,
|
|
439
469
|
last_attempt: ProvenanceQuantumAttemptModel | None,
|
|
440
470
|
) -> None:
|
|
441
471
|
for previous_attempt_log_extra in log_extra.previous_attempts:
|
|
@@ -448,8 +478,7 @@ class Scanner:
|
|
|
448
478
|
)
|
|
449
479
|
# We also need to get the logs from this extra provenance, since
|
|
450
480
|
# they won't be the main section of the log records.
|
|
451
|
-
|
|
452
|
-
result.log_model.attempts.append(log_extra.logs)
|
|
481
|
+
result.logs.attempts.append(log_extra.logs)
|
|
453
482
|
# The special last attempt is only appended after we attempt to
|
|
454
483
|
# read metadata later, but we have to append this one now.
|
|
455
484
|
result.attempts.append(quantum_attempt)
|
|
@@ -461,12 +490,11 @@ class Scanner:
|
|
|
461
490
|
# might get from the metadata has to come from this extra
|
|
462
491
|
# provenance in the logs.
|
|
463
492
|
quantum_attempt.exception = log_extra.exception
|
|
464
|
-
assert result.metadata_model is not None, "Only set to None after converting to JSON."
|
|
465
493
|
if log_extra.metadata is not None:
|
|
466
494
|
quantum_attempt.resource_usage = QuantumResourceUsage.from_task_metadata(log_extra.metadata)
|
|
467
|
-
result.
|
|
495
|
+
result.metadata.attempts.append(log_extra.metadata)
|
|
468
496
|
else:
|
|
469
|
-
result.
|
|
497
|
+
result.metadata.attempts.append(None)
|
|
470
498
|
# Regardless of whether this is the last attempt or not, we can only
|
|
471
499
|
# get the previous_process_quanta from the log extra.
|
|
472
500
|
quantum_attempt.previous_process_quanta.extend(log_extra.previous_process_quanta)
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
from __future__ import annotations
|
|
29
29
|
|
|
30
30
|
__all__ = (
|
|
31
|
+
"InProgressScan",
|
|
31
32
|
"IngestRequest",
|
|
32
33
|
"ScanReport",
|
|
33
|
-
"ScanResult",
|
|
34
34
|
"ScanStatus",
|
|
35
|
+
"WriteRequest",
|
|
35
36
|
)
|
|
36
37
|
|
|
37
38
|
import dataclasses
|
|
@@ -119,8 +120,8 @@ class IngestRequest:
|
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
@dataclasses.dataclass
|
|
122
|
-
class
|
|
123
|
-
"""A struct that represents
|
|
123
|
+
class InProgressScan:
|
|
124
|
+
"""A struct that represents a quantum that is being scanned."""
|
|
124
125
|
|
|
125
126
|
quantum_id: uuid.UUID
|
|
126
127
|
"""Unique ID for the quantum."""
|
|
@@ -131,30 +132,46 @@ class ScanResult:
|
|
|
131
132
|
attempts: list[ProvenanceQuantumAttemptModel] = dataclasses.field(default_factory=list)
|
|
132
133
|
"""Provenance information about each attempt to run the quantum."""
|
|
133
134
|
|
|
134
|
-
|
|
135
|
-
"""Unique IDs of the output datasets
|
|
135
|
+
outputs: dict[uuid.UUID, bool] = dataclasses.field(default_factory=dict)
|
|
136
|
+
"""Unique IDs of the output datasets mapped to whether they were actually
|
|
137
|
+
produced.
|
|
138
|
+
"""
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
default_factory=ProvenanceTaskMetadataModel
|
|
139
|
-
)
|
|
140
|
+
metadata: ProvenanceTaskMetadataModel = dataclasses.field(default_factory=ProvenanceTaskMetadataModel)
|
|
140
141
|
"""Task metadata information for each attempt.
|
|
142
|
+
"""
|
|
141
143
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
logs: ProvenanceLogRecordsModel = dataclasses.field(default_factory=ProvenanceLogRecordsModel)
|
|
145
|
+
"""Log records for each attempt.
|
|
144
146
|
"""
|
|
145
147
|
|
|
146
|
-
metadata_content: bytes = b""
|
|
147
|
-
"""Serialized form of `metadata_model`."""
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
@dataclasses.dataclass
|
|
150
|
+
class WriteRequest:
|
|
151
|
+
"""A struct that represents a request to write provenance for a quantum."""
|
|
152
|
+
|
|
153
|
+
quantum_id: uuid.UUID
|
|
154
|
+
"""Unique ID for the quantum."""
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
status: ScanStatus
|
|
157
|
+
"""Combined status for the scan and the execution of the quantum."""
|
|
158
|
+
|
|
159
|
+
existing_outputs: set[uuid.UUID] = dataclasses.field(default_factory=set)
|
|
160
|
+
"""Unique IDs of the output datasets that were actually written."""
|
|
161
|
+
|
|
162
|
+
quantum: bytes = b""
|
|
163
|
+
"""Serialized quantum provenance model.
|
|
164
|
+
|
|
165
|
+
This may be empty for quanta that had no attempts.
|
|
154
166
|
"""
|
|
155
167
|
|
|
156
|
-
|
|
157
|
-
"""Serialized
|
|
168
|
+
metadata: bytes = b""
|
|
169
|
+
"""Serialized task metadata."""
|
|
170
|
+
|
|
171
|
+
logs: bytes = b""
|
|
172
|
+
"""Serialized logs."""
|
|
158
173
|
|
|
159
174
|
is_compressed: bool = False
|
|
160
|
-
"""Whether the `metadata
|
|
175
|
+
"""Whether the `quantum`, `metadata`, and `log` attributes are
|
|
176
|
+
compressed.
|
|
177
|
+
"""
|
|
@@ -30,6 +30,7 @@ from __future__ import annotations
|
|
|
30
30
|
__all__ = ("aggregate_graph",)
|
|
31
31
|
|
|
32
32
|
import dataclasses
|
|
33
|
+
import itertools
|
|
33
34
|
import uuid
|
|
34
35
|
|
|
35
36
|
import astropy.units as u
|
|
@@ -53,7 +54,7 @@ from ._communicators import (
|
|
|
53
54
|
from ._config import AggregatorConfig
|
|
54
55
|
from ._ingester import Ingester
|
|
55
56
|
from ._scanner import Scanner
|
|
56
|
-
from ._structs import ScanReport,
|
|
57
|
+
from ._structs import ScanReport, ScanStatus, WriteRequest
|
|
57
58
|
from ._writer import Writer
|
|
58
59
|
|
|
59
60
|
|
|
@@ -87,19 +88,15 @@ class Supervisor:
|
|
|
87
88
|
reader.read_init_quanta()
|
|
88
89
|
self.predicted = reader.components
|
|
89
90
|
self.comms.progress.log.info("Analyzing predicted graph.")
|
|
90
|
-
|
|
91
|
-
quantum_index: quantum_id for quantum_id, quantum_index in self.predicted.quantum_indices.items()
|
|
92
|
-
}
|
|
93
|
-
xgraph = networkx.DiGraph(
|
|
94
|
-
[(uuid_by_index[a], uuid_by_index[b]) for a, b in self.predicted.thin_graph.edges]
|
|
95
|
-
)
|
|
91
|
+
xgraph = networkx.DiGraph(self.predicted.thin_graph.edges)
|
|
96
92
|
# Make sure all quanta are in the graph, even if they don't have any
|
|
97
93
|
# quantum-only edges.
|
|
98
|
-
|
|
94
|
+
for thin_quantum in itertools.chain.from_iterable(self.predicted.thin_graph.quanta.values()):
|
|
95
|
+
xgraph.add_node(thin_quantum.quantum_id)
|
|
99
96
|
# Add init quanta as nodes without edges, because the scanner should
|
|
100
97
|
# only be run after init outputs are all written and hence we don't
|
|
101
98
|
# care when we process them.
|
|
102
|
-
for init_quantum in self.predicted.init_quanta.root
|
|
99
|
+
for init_quantum in self.predicted.init_quanta.root:
|
|
103
100
|
xgraph.add_node(init_quantum.quantum_id)
|
|
104
101
|
self.walker = GraphWalker(xgraph)
|
|
105
102
|
|
|
@@ -137,7 +134,7 @@ class Supervisor:
|
|
|
137
134
|
blocked_quanta = self.walker.fail(scan_report.quantum_id)
|
|
138
135
|
for blocked_quantum_id in blocked_quanta:
|
|
139
136
|
if self.comms.config.output_path is not None:
|
|
140
|
-
self.comms.request_write(
|
|
137
|
+
self.comms.request_write(WriteRequest(blocked_quantum_id, status=ScanStatus.BLOCKED))
|
|
141
138
|
self.comms.progress.scans.update(1)
|
|
142
139
|
self.comms.progress.quantum_ingests.update(len(blocked_quanta))
|
|
143
140
|
case ScanStatus.ABANDONED:
|