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.
Files changed (22) hide show
  1. lsst/pipe/base/connections.py +11 -0
  2. lsst/pipe/base/quantum_graph/_common.py +15 -1
  3. lsst/pipe/base/quantum_graph/_multiblock.py +14 -39
  4. lsst/pipe/base/quantum_graph/_predicted.py +77 -73
  5. lsst/pipe/base/quantum_graph/_provenance.py +73 -144
  6. lsst/pipe/base/quantum_graph/aggregator/_communicators.py +10 -10
  7. lsst/pipe/base/quantum_graph/aggregator/_scanner.py +88 -60
  8. lsst/pipe/base/quantum_graph/aggregator/_structs.py +36 -19
  9. lsst/pipe/base/quantum_graph/aggregator/_supervisor.py +7 -10
  10. lsst/pipe/base/quantum_graph/aggregator/_writer.py +55 -144
  11. lsst/pipe/base/quantum_graph_builder.py +0 -1
  12. lsst/pipe/base/version.py +1 -1
  13. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/METADATA +1 -1
  14. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/RECORD +22 -22
  15. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/WHEEL +0 -0
  16. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/entry_points.txt +0 -0
  17. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/COPYRIGHT +0 -0
  18. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/LICENSE +0 -0
  19. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/bsd_license.txt +0 -0
  20. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/licenses/gpl-v3.0.txt +0 -0
  21. {lsst_pipe_base-29.2025.4600.dist-info → lsst_pipe_base-29.2025.4800.dist-info}/top_level.txt +0 -0
  22. {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, ScanResult, ScanStatus
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) -> ScanResult:
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 : `ScanResult`
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 = ScanResult(predicted_quantum.quantum_id, status=ScanStatus.INIT)
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 = ScanResult(predicted_quantum.quantum_id, ScanStatus.INCOMPLETE)
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.log_model.attempts) < len(result.attempts):
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.log_model.attempts.append(None)
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.metadata_model.attempts) < len(result.attempts):
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.metadata_model.attempts.append(None)
254
- assert len(result.log_model.attempts) == len(result.attempts) or len(
255
- result.metadata_model.attempts
256
- ) == len(result.attempts), (
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
- # as having been definitively written, as well as and the metadata and
276
- # logs themselves (since we just checked those).
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.existing_outputs and self.scan_dataset(
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.comms.request_write(result)
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: ScanResult
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 : `ScanResult`
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.existing_outputs:
313
- predicted_output = predicted_outputs_by_id[dataset_id]
314
- to_ingest_predicted.append(predicted_output)
315
- to_ingest_refs.append(self.reader.components.make_dataset_ref(predicted_output))
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: ScanResult,
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 : `ScanResult`
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.existing_outputs.add(ref.id)
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
- result.existing_outputs.update(
375
- uuid.UUID(id_str) for id_str in ensure_iterable(metadata["quantum"].getArray("outputs"))
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
- assert result.metadata_model is not None, "Only set to None after converting to JSON."
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: ScanResult,
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 : `ScanResult`
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.existing_outputs.add(ref.id)
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
- assert result.log_model is not None, "Only set to None after converting to JSON."
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: ScanResult,
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
- assert result.log_model is not None, "Only set to None after converting to JSON."
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.metadata_model.attempts.append(log_extra.metadata)
495
+ result.metadata.attempts.append(log_extra.metadata)
468
496
  else:
469
- result.metadata_model.attempts.append(None)
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 ScanResult:
123
- """A struct that represents the result of scanning a quantum."""
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
- existing_outputs: set[uuid.UUID] = dataclasses.field(default_factory=set)
135
- """Unique IDs of the output datasets that were actually written."""
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
- metadata_model: ProvenanceTaskMetadataModel | None = dataclasses.field(
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
- This is set to `None` to keep the pickle size small after it is saved
143
- to `metadata_content`.
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
- log_model: ProvenanceLogRecordsModel | None = dataclasses.field(default_factory=ProvenanceLogRecordsModel)
150
- """Log records for each attempt.
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
- This is set to `None` to keep the pickle size small after it is saved
153
- to `log_content`.
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
- log_content: bytes = b""
157
- """Serialized form of `logs_model`."""
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` and `log` attributes are compressed."""
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, ScanResult, ScanStatus
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
- uuid_by_index = {
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
- xgraph.add_nodes_from(uuid_by_index.values())
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[1:]: # skip 'packages' producer
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(ScanResult(blocked_quantum_id, status=ScanStatus.BLOCKED))
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: