reader-workbench 1.0.0__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.
- reader_workbench/__init__.py +22 -0
- reader_workbench/__main__.py +4 -0
- reader_workbench/_version.py +17 -0
- reader_workbench/api/__init__.py +74 -0
- reader_workbench/api/_record_reads.py +75 -0
- reader_workbench/api/artifacts.py +79 -0
- reader_workbench/api/facade.py +538 -0
- reader_workbench/api/models.py +285 -0
- reader_workbench/api/notebooks.py +63 -0
- reader_workbench/contracts/__init__.py +18 -0
- reader_workbench/contracts/builtins/__init__.py +36 -0
- reader_workbench/contracts/builtins/cytometry.py +140 -0
- reader_workbench/contracts/builtins/four_state_event_window.py +214 -0
- reader_workbench/contracts/builtins/generic.py +21 -0
- reader_workbench/contracts/builtins/logic.py +149 -0
- reader_workbench/contracts/builtins/plate_reader.py +47 -0
- reader_workbench/contracts/catalog.py +257 -0
- reader_workbench/contracts/model.py +109 -0
- reader_workbench/domains/__init__.py +1 -0
- reader_workbench/domains/cytometry/__init__.py +3 -0
- reader_workbench/domains/cytometry/analysis/__init__.py +29 -0
- reader_workbench/domains/cytometry/analysis/events.py +182 -0
- reader_workbench/domains/cytometry/analysis/gating.py +175 -0
- reader_workbench/domains/cytometry/analysis/workflow.py +248 -0
- reader_workbench/domains/cytometry/io/__init__.py +3 -0
- reader_workbench/domains/cytometry/io/fcs.py +135 -0
- reader_workbench/domains/cytometry/plots/__init__.py +5 -0
- reader_workbench/domains/cytometry/plots/diagnostic.py +155 -0
- reader_workbench/domains/logic/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/pairs.py +661 -0
- reader_workbench/domains/logic/four_state_vector/__init__.py +7 -0
- reader_workbench/domains/logic/four_state_vector/builder.py +321 -0
- reader_workbench/domains/logic/four_state_vector/collection/__init__.py +20 -0
- reader_workbench/domains/logic/four_state_vector/collection/checks.py +49 -0
- reader_workbench/domains/logic/four_state_vector/collection/constants.py +29 -0
- reader_workbench/domains/logic/four_state_vector/collection/model.py +19 -0
- reader_workbench/domains/logic/four_state_vector/collection/render.py +383 -0
- reader_workbench/domains/logic/four_state_vector/collection/sources.py +185 -0
- reader_workbench/domains/logic/four_state_vector/config.py +214 -0
- reader_workbench/domains/logic/four_state_vector/diagnostic.py +361 -0
- reader_workbench/domains/logic/four_state_vector/heatmap.py +86 -0
- reader_workbench/domains/logic/four_state_vector/math.py +191 -0
- reader_workbench/domains/logic/four_state_vector/reference.py +85 -0
- reader_workbench/domains/logic/four_state_vector/selection.py +228 -0
- reader_workbench/domains/logic/four_state_vector/treatment_semantics.py +51 -0
- reader_workbench/domains/logic/four_state_vector/validation.py +19 -0
- reader_workbench/domains/logic/logic_symmetry/__init__.py +3 -0
- reader_workbench/domains/logic/logic_symmetry/encodings.py +93 -0
- reader_workbench/domains/logic/logic_symmetry/extract_corners.py +192 -0
- reader_workbench/domains/logic/logic_symmetry/main.py +236 -0
- reader_workbench/domains/logic/logic_symmetry/metrics.py +96 -0
- reader_workbench/domains/logic/logic_symmetry/overlay.py +129 -0
- reader_workbench/domains/logic/logic_symmetry/prep.py +138 -0
- reader_workbench/domains/logic/logic_symmetry/render.py +356 -0
- reader_workbench/domains/logic/treatment_columns.py +42 -0
- reader_workbench/domains/plate_reader/__init__.py +1 -0
- reader_workbench/domains/plate_reader/analysis/__init__.py +14 -0
- reader_workbench/domains/plate_reader/analysis/fold_change.py +474 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/__init__.py +21 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/aggregation.py +191 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contract_fields.py +50 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contracts.py +320 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/design_dispositions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/disposition_records.py +140 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/event_sensitivity.py +27 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/materialize.py +274 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/observation_resampling.py +97 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/reduction.py +62 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/seeds.py +15 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/sources.py +308 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusion_validation.py +94 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/timepoints.py +76 -0
- reader_workbench/domains/plate_reader/io/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/sample_map.py +65 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_kinetic.py +141 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_parser.py +295 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_shared.py +195 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_snapshot.py +130 -0
- reader_workbench/domains/plate_reader/ordering.py +59 -0
- reader_workbench/domains/plate_reader/plots/__init__.py +15 -0
- reader_workbench/domains/plate_reader/plots/_data.py +29 -0
- reader_workbench/domains/plate_reader/plots/common.py +346 -0
- reader_workbench/domains/plate_reader/plots/distributions.py +324 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych.py +525 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych_render.py +195 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/__init__.py +26 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic.py +295 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_components.py +149 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_render.py +308 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_style.py +51 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/schema.py +8 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/summary.py +140 -0
- reader_workbench/domains/plate_reader/plots/grouping.py +53 -0
- reader_workbench/domains/plate_reader/plots/panels/__init__.py +12 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot.py +161 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot_data.py +91 -0
- reader_workbench/domains/plate_reader/plots/panels/time_series.py +296 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic.py +435 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic_render.py +300 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/__init__.py +315 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/planning.py +168 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/__init__.py +205 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/inputs.py +103 -0
- reader_workbench/domains/plate_reader/plots/time_series.py +317 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/__init__.py +479 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/planning.py +283 -0
- reader_workbench/domains/time_series/__init__.py +29 -0
- reader_workbench/domains/time_series/aggregation.py +60 -0
- reader_workbench/domains/time_series/contracts.py +368 -0
- reader_workbench/domains/time_series/reduction.py +395 -0
- reader_workbench/errors.py +55 -0
- reader_workbench/maintenance/__init__.py +6 -0
- reader_workbench/maintenance/docs.py +335 -0
- reader_workbench/maintenance/model.py +28 -0
- reader_workbench/maintenance/release.py +39 -0
- reader_workbench/maintenance/skills.py +124 -0
- reader_workbench/plotting/__init__.py +20 -0
- reader_workbench/plotting/mpl.py +56 -0
- reader_workbench/plotting/sinks.py +69 -0
- reader_workbench/plotting/style.py +175 -0
- reader_workbench/plotting/utils.py +27 -0
- reader_workbench/plugins/__init__.py +1 -0
- reader_workbench/plugins/catalog.py +33 -0
- reader_workbench/plugins/export/__init__.py +0 -0
- reader_workbench/plugins/export/_paths.py +21 -0
- reader_workbench/plugins/export/csv.py +41 -0
- reader_workbench/plugins/export/xlsx.py +44 -0
- reader_workbench/plugins/ingest/__init__.py +0 -0
- reader_workbench/plugins/ingest/_discovery.py +58 -0
- reader_workbench/plugins/ingest/discovery_policy.py +66 -0
- reader_workbench/plugins/ingest/flow_cytometer.py +139 -0
- reader_workbench/plugins/ingest/synergy_h1.py +234 -0
- reader_workbench/plugins/manifests/__init__.py +1 -0
- reader_workbench/plugins/manifests/export.py +29 -0
- reader_workbench/plugins/manifests/ingest.py +29 -0
- reader_workbench/plugins/manifests/plot.py +161 -0
- reader_workbench/plugins/manifests/transform.py +172 -0
- reader_workbench/plugins/manifests/validator.py +18 -0
- reader_workbench/plugins/plot/__init__.py +0 -0
- reader_workbench/plugins/plot/_shared.py +55 -0
- reader_workbench/plugins/plot/cytometry_diagnostic.py +54 -0
- reader_workbench/plugins/plot/distributions.py +58 -0
- reader_workbench/plugins/plot/dual_reporter_triptych.py +224 -0
- reader_workbench/plugins/plot/four_state_event_window_diagnostic.py +133 -0
- reader_workbench/plugins/plot/four_state_event_window_summary.py +53 -0
- reader_workbench/plugins/plot/four_state_vector_collection.py +45 -0
- reader_workbench/plugins/plot/four_state_vector_diagnostic.py +105 -0
- reader_workbench/plugins/plot/four_state_vector_heatmap.py +63 -0
- reader_workbench/plugins/plot/logic_symmetry.py +56 -0
- reader_workbench/plugins/plot/single_reporter_diagnostic.py +279 -0
- reader_workbench/plugins/plot/snapshot_barplot.py +65 -0
- reader_workbench/plugins/plot/snapshot_heatmap.py +104 -0
- reader_workbench/plugins/plot/time_series.py +114 -0
- reader_workbench/plugins/plot/ts_and_snap.py +210 -0
- reader_workbench/plugins/transform/__init__.py +0 -0
- reader_workbench/plugins/transform/_four_state_vector.py +204 -0
- reader_workbench/plugins/transform/_labeling.py +109 -0
- reader_workbench/plugins/transform/alias.py +70 -0
- reader_workbench/plugins/transform/assay_labels.py +62 -0
- reader_workbench/plugins/transform/blank.py +79 -0
- reader_workbench/plugins/transform/crosstalk_pairs.py +180 -0
- reader_workbench/plugins/transform/cytometry_gating.py +120 -0
- reader_workbench/plugins/transform/fold_change.py +79 -0
- reader_workbench/plugins/transform/four_state_event_window.py +93 -0
- reader_workbench/plugins/transform/four_state_vector.py +62 -0
- reader_workbench/plugins/transform/four_state_vector_collection.py +41 -0
- reader_workbench/plugins/transform/logic_symmetry.py +67 -0
- reader_workbench/plugins/transform/outlier_filter.py +60 -0
- reader_workbench/plugins/transform/overflow.py +197 -0
- reader_workbench/plugins/transform/ratio.py +237 -0
- reader_workbench/plugins/transform/sample_map.py +170 -0
- reader_workbench/plugins/transform/sample_metadata.py +94 -0
- reader_workbench/plugins/validator/__init__.py +1 -0
- reader_workbench/plugins/validator/to_tidy_plus_map.py +155 -0
- reader_workbench/protocols/__init__.py +80 -0
- reader_workbench/protocols/_builtins_plate_reader_growth.py +179 -0
- reader_workbench/protocols/_builtins_plate_reader_variants.py +274 -0
- reader_workbench/protocols/builtins.py +1656 -0
- reader_workbench/protocols/compiler.py +22 -0
- reader_workbench/protocols/compilers/__init__.py +1 -0
- reader_workbench/protocols/compilers/common.py +100 -0
- reader_workbench/protocols/compilers/cytometry.py +87 -0
- reader_workbench/protocols/compilers/generic.py +14 -0
- reader_workbench/protocols/compilers/logic.py +245 -0
- reader_workbench/protocols/compilers/plate_reader.py +937 -0
- reader_workbench/protocols/compilers/plate_reader_pipeline.py +197 -0
- reader_workbench/protocols/model.py +1486 -0
- reader_workbench/protocols/semantic_coverage.py +234 -0
- reader_workbench/runtime/__init__.py +12 -0
- reader_workbench/runtime/builtin.py +23 -0
- reader_workbench/runtime/model.py +42 -0
- reader_workbench/workbench/__init__.py +60 -0
- reader_workbench/workbench/assets/__init__.py +22 -0
- reader_workbench/workbench/assets/types.py +118 -0
- reader_workbench/workbench/audit/__init__.py +5 -0
- reader_workbench/workbench/audit/experiments.py +307 -0
- reader_workbench/workbench/audit/staging.py +187 -0
- reader_workbench/workbench/cli/__init__.py +51 -0
- reader_workbench/workbench/cli/_lazy.py +9 -0
- reader_workbench/workbench/cli/_records_view.py +150 -0
- reader_workbench/workbench/cli/_surface_execution.py +443 -0
- reader_workbench/workbench/cli/audit.py +95 -0
- reader_workbench/workbench/cli/automation.py +229 -0
- reader_workbench/workbench/cli/demo.py +46 -0
- reader_workbench/workbench/cli/dop.py +91 -0
- reader_workbench/workbench/cli/experiments.py +635 -0
- reader_workbench/workbench/cli/helpers.py +232 -0
- reader_workbench/workbench/cli/main.py +59 -0
- reader_workbench/workbench/cli/maintenance.py +82 -0
- reader_workbench/workbench/cli/notebooks.py +260 -0
- reader_workbench/workbench/cli/pagination.py +117 -0
- reader_workbench/workbench/cli/protocols.py +336 -0
- reader_workbench/workbench/cli/shared.py +309 -0
- reader_workbench/workbench/cli/surfaces.py +534 -0
- reader_workbench/workbench/cli/verification.py +128 -0
- reader_workbench/workbench/commands.py +10 -0
- reader_workbench/workbench/config/__init__.py +47 -0
- reader_workbench/workbench/config/identity.py +13 -0
- reader_workbench/workbench/config/load.py +405 -0
- reader_workbench/workbench/config/model.py +274 -0
- reader_workbench/workbench/context.py +26 -0
- reader_workbench/workbench/decl/__init__.py +31 -0
- reader_workbench/workbench/decl/build.py +190 -0
- reader_workbench/workbench/decl/model.py +81 -0
- reader_workbench/workbench/dop/__init__.py +12 -0
- reader_workbench/workbench/dop/builtins.py +261 -0
- reader_workbench/workbench/dop/model.py +209 -0
- reader_workbench/workbench/engine/__init__.py +42 -0
- reader_workbench/workbench/engine/_shared.py +76 -0
- reader_workbench/workbench/engine/contracts.py +283 -0
- reader_workbench/workbench/engine/execution.py +326 -0
- reader_workbench/workbench/engine/file_outputs.py +260 -0
- reader_workbench/workbench/engine/inputs.py +161 -0
- reader_workbench/workbench/engine/invocations.py +507 -0
- reader_workbench/workbench/engine/planning.py +72 -0
- reader_workbench/workbench/engine/runtime.py +464 -0
- reader_workbench/workbench/engine/setup.py +149 -0
- reader_workbench/workbench/engine/validation.py +684 -0
- reader_workbench/workbench/experiment/__init__.py +47 -0
- reader_workbench/workbench/experiment/model.py +381 -0
- reader_workbench/workbench/experiments.py +133 -0
- reader_workbench/workbench/graph/__init__.py +47 -0
- reader_workbench/workbench/graph/nodes.py +102 -0
- reader_workbench/workbench/graph/normalize.py +177 -0
- reader_workbench/workbench/graph/refs.py +148 -0
- reader_workbench/workbench/input_discovery.py +19 -0
- reader_workbench/workbench/inspection/__init__.py +3 -0
- reader_workbench/workbench/inspection/catalogs.py +128 -0
- reader_workbench/workbench/inspection/common.py +92 -0
- reader_workbench/workbench/inspection/dop.py +64 -0
- reader_workbench/workbench/inspection/experiments.py +449 -0
- reader_workbench/workbench/inspection/inventory.py +68 -0
- reader_workbench/workbench/inspection/protocols.py +368 -0
- reader_workbench/workbench/inspection/readiness.py +333 -0
- reader_workbench/workbench/inspection/reports.py +367 -0
- reader_workbench/workbench/inspection/results.py +166 -0
- reader_workbench/workbench/inspection/runtime.py +287 -0
- reader_workbench/workbench/inspection/semantics.py +192 -0
- reader_workbench/workbench/inspection/validation.py +30 -0
- reader_workbench/workbench/notebooks/__init__.py +17 -0
- reader_workbench/workbench/notebooks/_launch_registry.py +112 -0
- reader_workbench/workbench/notebooks/_launch_runtime.py +104 -0
- reader_workbench/workbench/notebooks/components/__init__.py +21 -0
- reader_workbench/workbench/notebooks/components/deliverables.py +403 -0
- reader_workbench/workbench/notebooks/components/overview.py +119 -0
- reader_workbench/workbench/notebooks/eda.marimo.py.txt +153 -0
- reader_workbench/workbench/notebooks/launch.py +274 -0
- reader_workbench/workbench/notebooks/presentation.py +136 -0
- reader_workbench/workbench/notebooks/scaffold.py +60 -0
- reader_workbench/workbench/ontology.py +78 -0
- reader_workbench/workbench/paths.py +44 -0
- reader_workbench/workbench/ports/__init__.py +31 -0
- reader_workbench/workbench/ports/model.py +168 -0
- reader_workbench/workbench/records/__init__.py +44 -0
- reader_workbench/workbench/records/epoch.py +329 -0
- reader_workbench/workbench/records/evidence.py +247 -0
- reader_workbench/workbench/records/identity.py +87 -0
- reader_workbench/workbench/records/locking.py +185 -0
- reader_workbench/workbench/records/model.py +711 -0
- reader_workbench/workbench/records/sources.py +73 -0
- reader_workbench/workbench/records/store.py +1022 -0
- reader_workbench/workbench/records/verification.py +998 -0
- reader_workbench/workbench/registry.py +333 -0
- reader_workbench/workbench/spec_overrides.py +215 -0
- reader_workbench-1.0.0.dist-info/METADATA +91 -0
- reader_workbench-1.0.0.dist-info/RECORD +293 -0
- reader_workbench-1.0.0.dist-info/WHEEL +5 -0
- reader_workbench-1.0.0.dist-info/entry_points.txt +2 -0
- reader_workbench-1.0.0.dist-info/licenses/LICENSE +21 -0
- reader_workbench-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rich import box
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from reader_workbench.errors import ConfigError, ExecutionError, InvocationFinalizationError
|
|
13
|
+
from reader_workbench.plotting.mpl import ensure_mpl_cache_dir
|
|
14
|
+
from reader_workbench.runtime import ReaderRuntime, builtin_runtime
|
|
15
|
+
from reader_workbench.workbench.commands import reader_command
|
|
16
|
+
from reader_workbench.workbench.decl import WorkbenchDecl, load_workbench_decl
|
|
17
|
+
from reader_workbench.workbench.graph import ensure_unique_workbench_ids, resolve_workbench
|
|
18
|
+
from reader_workbench.workbench.paths import resolve_confined_sink_root
|
|
19
|
+
from reader_workbench.workbench.records import current_build_identity
|
|
20
|
+
from reader_workbench.workbench.records.locking import provenance_lock_scope
|
|
21
|
+
|
|
22
|
+
from ._shared import collect_categories
|
|
23
|
+
from .execution import run_steps
|
|
24
|
+
from .invocations import (
|
|
25
|
+
ExecutionResult,
|
|
26
|
+
InvocationLedger,
|
|
27
|
+
ProducedRecordRevision,
|
|
28
|
+
SelectedSteps,
|
|
29
|
+
capture_revision_snapshot,
|
|
30
|
+
declared_input_projection,
|
|
31
|
+
produced_record_revisions,
|
|
32
|
+
)
|
|
33
|
+
from .planning import build_next_steps, explain
|
|
34
|
+
from .setup import (
|
|
35
|
+
build_run_context,
|
|
36
|
+
close_reader_logger,
|
|
37
|
+
configure_logger,
|
|
38
|
+
normalize_log_level,
|
|
39
|
+
resolve_palette_book,
|
|
40
|
+
slice_pipeline_steps,
|
|
41
|
+
)
|
|
42
|
+
from .validation import (
|
|
43
|
+
_assert_no_source_record_output_collisions,
|
|
44
|
+
_planned_output_record_ids,
|
|
45
|
+
_source_record_preflight_issues,
|
|
46
|
+
validation_summary,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_spec(
|
|
51
|
+
decl: WorkbenchDecl,
|
|
52
|
+
*,
|
|
53
|
+
resume_from: str | None = None,
|
|
54
|
+
until: str | None = None,
|
|
55
|
+
reset_records: bool = False,
|
|
56
|
+
dry_run: bool = False,
|
|
57
|
+
log_level: str = "INFO",
|
|
58
|
+
verbose: bool = True,
|
|
59
|
+
console: Console | None = None,
|
|
60
|
+
include_pipeline: bool = True,
|
|
61
|
+
include_plots: bool = True,
|
|
62
|
+
include_exports: bool = True,
|
|
63
|
+
plot_specs=None,
|
|
64
|
+
export_specs=None,
|
|
65
|
+
job_label: str | None = None,
|
|
66
|
+
show_next_steps: bool = False,
|
|
67
|
+
runtime: ReaderRuntime | None = None,
|
|
68
|
+
) -> ExecutionResult:
|
|
69
|
+
os.environ.setdefault("ARROW_LOG_LEVEL", "FATAL")
|
|
70
|
+
runtime = runtime or builtin_runtime()
|
|
71
|
+
layout = decl.experiment_semantics.layout
|
|
72
|
+
try:
|
|
73
|
+
out_dir = resolve_confined_sink_root(layout.outputs_dir, root=decl.experiment.root, label="outputs")
|
|
74
|
+
except ValueError as exc:
|
|
75
|
+
raise ConfigError(str(exc)) from exc
|
|
76
|
+
if out_dir.exists() and not out_dir.is_dir():
|
|
77
|
+
raise ConfigError(f"paths.outputs points to a file: {out_dir}")
|
|
78
|
+
|
|
79
|
+
console = console or Console()
|
|
80
|
+
normalize_log_level(log_level)
|
|
81
|
+
|
|
82
|
+
workbench = resolve_workbench(decl)
|
|
83
|
+
pipeline_steps = list(workbench.pipeline)
|
|
84
|
+
plot_steps = list(plot_specs) if plot_specs is not None else list(workbench.plots)
|
|
85
|
+
export_steps = list(export_specs) if export_specs is not None else list(workbench.exports)
|
|
86
|
+
ensure_unique_workbench_ids(pipeline_steps, plot_steps, export_steps)
|
|
87
|
+
|
|
88
|
+
if not include_pipeline:
|
|
89
|
+
if resume_from or until:
|
|
90
|
+
raise ConfigError(
|
|
91
|
+
f"--from/--until require pipeline execution; use {reader_command('run')} for sliced runs."
|
|
92
|
+
)
|
|
93
|
+
pipeline_steps = []
|
|
94
|
+
else:
|
|
95
|
+
pipeline_steps = slice_pipeline_steps(pipeline_steps, resume_from=resume_from, until=until)
|
|
96
|
+
|
|
97
|
+
if reset_records:
|
|
98
|
+
if dry_run:
|
|
99
|
+
raise ConfigError("reset_records cannot be used for a dry run")
|
|
100
|
+
if not include_pipeline or pipeline_steps != list(workbench.pipeline):
|
|
101
|
+
raise ConfigError("reset_records requires a complete pipeline run without --from, --until, or --only")
|
|
102
|
+
|
|
103
|
+
if not include_plots:
|
|
104
|
+
plot_steps = []
|
|
105
|
+
if not include_exports:
|
|
106
|
+
export_steps = []
|
|
107
|
+
|
|
108
|
+
all_steps = pipeline_steps + plot_steps + export_steps
|
|
109
|
+
categories = collect_categories(list(workbench.plugin_steps()))
|
|
110
|
+
registry = runtime.plugins if categories else None
|
|
111
|
+
selected_steps = SelectedSteps(
|
|
112
|
+
pipeline=tuple(step.id for step in pipeline_steps),
|
|
113
|
+
plots=tuple(step.id for step in plot_steps),
|
|
114
|
+
exports=tuple(step.id for step in export_steps),
|
|
115
|
+
)
|
|
116
|
+
requested_operations = [
|
|
117
|
+
operation
|
|
118
|
+
for operation, enabled in (
|
|
119
|
+
("run", include_pipeline),
|
|
120
|
+
("plot", include_plots),
|
|
121
|
+
("export", include_exports),
|
|
122
|
+
)
|
|
123
|
+
if enabled
|
|
124
|
+
]
|
|
125
|
+
operation = requested_operations[0] if len(requested_operations) == 1 else "mixed"
|
|
126
|
+
|
|
127
|
+
if dry_run:
|
|
128
|
+
validation_summary(
|
|
129
|
+
decl,
|
|
130
|
+
check_files=False,
|
|
131
|
+
exp_root=decl.experiment.root,
|
|
132
|
+
runtime=runtime,
|
|
133
|
+
plot_specs_override=plot_steps,
|
|
134
|
+
export_specs_override=export_steps,
|
|
135
|
+
)
|
|
136
|
+
console.print(Panel.fit("DRY RUN — printing plan", border_style="yellow", box=box.ROUNDED))
|
|
137
|
+
explain(
|
|
138
|
+
decl,
|
|
139
|
+
console=console,
|
|
140
|
+
registry=registry,
|
|
141
|
+
plot_specs=plot_steps,
|
|
142
|
+
export_specs=export_steps,
|
|
143
|
+
runtime=runtime,
|
|
144
|
+
)
|
|
145
|
+
return ExecutionResult(
|
|
146
|
+
invocation_id=None,
|
|
147
|
+
provenance_epoch_id=None,
|
|
148
|
+
operation=operation,
|
|
149
|
+
status="planned",
|
|
150
|
+
dry_run=True,
|
|
151
|
+
selected_steps=selected_steps,
|
|
152
|
+
produced_record_revisions=(),
|
|
153
|
+
ledger_path=None,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if registry is not None:
|
|
157
|
+
planned_record_ids = _planned_output_record_ids(
|
|
158
|
+
pipeline_items=pipeline_steps,
|
|
159
|
+
plot_items=plot_steps,
|
|
160
|
+
export_items=export_steps,
|
|
161
|
+
registry=registry,
|
|
162
|
+
)
|
|
163
|
+
_assert_no_source_record_output_collisions(
|
|
164
|
+
items=pipeline_steps,
|
|
165
|
+
planned_record_ids=planned_record_ids,
|
|
166
|
+
experiment_root=decl.experiment.root,
|
|
167
|
+
)
|
|
168
|
+
_, source_record_issues = _source_record_preflight_issues(
|
|
169
|
+
items=pipeline_steps,
|
|
170
|
+
registry=registry,
|
|
171
|
+
contracts=runtime.contracts,
|
|
172
|
+
)
|
|
173
|
+
if source_record_issues:
|
|
174
|
+
raise ConfigError("Run preflight failed before output mutation: " + source_record_issues[0])
|
|
175
|
+
|
|
176
|
+
plots_cfg = layout.plots_subdir
|
|
177
|
+
exports_cfg = layout.exports_subdir
|
|
178
|
+
store = runtime.record_store(
|
|
179
|
+
out_dir,
|
|
180
|
+
plots_subdir=(plots_cfg if plots_cfg not in ("", ".", "./") else None),
|
|
181
|
+
exports_subdir=(exports_cfg if exports_cfg not in ("", ".", "./") else None),
|
|
182
|
+
experiment_root=decl.experiment.root,
|
|
183
|
+
create=False,
|
|
184
|
+
)
|
|
185
|
+
preserved_output_paths = (layout.subdir_path("notebooks"),)
|
|
186
|
+
store.validate_no_interrupted_epoch()
|
|
187
|
+
if reset_records:
|
|
188
|
+
store.validate_generated_epoch_reset(preserved_paths=preserved_output_paths)
|
|
189
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
# The existing writer lease lives under manifests. Create only its parent
|
|
191
|
+
# before acquisition; catalog and generated-output layout belong inside the
|
|
192
|
+
# guarded section below.
|
|
193
|
+
store.manifests_dir.mkdir(parents=True, exist_ok=True)
|
|
194
|
+
with provenance_lock_scope(
|
|
195
|
+
store.provenance_lock,
|
|
196
|
+
acquire_error=ExecutionError("Could not acquire the experiment writer lease"),
|
|
197
|
+
release_error=ExecutionError(
|
|
198
|
+
"Execution completed, but Reader could not release the experiment writer lease. "
|
|
199
|
+
"Do not retry blindly; run reader verify before continuing."
|
|
200
|
+
),
|
|
201
|
+
release_note="Reader also could not release the experiment writer lease",
|
|
202
|
+
):
|
|
203
|
+
return _run_spec_locked(
|
|
204
|
+
decl=decl,
|
|
205
|
+
runtime=runtime,
|
|
206
|
+
out_dir=out_dir,
|
|
207
|
+
store=store,
|
|
208
|
+
log_level=log_level,
|
|
209
|
+
all_steps=all_steps,
|
|
210
|
+
pipeline_steps=pipeline_steps,
|
|
211
|
+
plot_steps=plot_steps,
|
|
212
|
+
export_steps=export_steps,
|
|
213
|
+
registry=registry,
|
|
214
|
+
selected_steps=selected_steps,
|
|
215
|
+
operation=operation,
|
|
216
|
+
reset_records=reset_records,
|
|
217
|
+
verbose=verbose,
|
|
218
|
+
console=console,
|
|
219
|
+
include_pipeline=include_pipeline,
|
|
220
|
+
show_next_steps=show_next_steps,
|
|
221
|
+
job_label=job_label,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _run_spec_locked(
|
|
226
|
+
*,
|
|
227
|
+
decl: WorkbenchDecl,
|
|
228
|
+
runtime: ReaderRuntime,
|
|
229
|
+
out_dir: Path,
|
|
230
|
+
store: Any,
|
|
231
|
+
log_level: str,
|
|
232
|
+
all_steps: list[Any],
|
|
233
|
+
pipeline_steps: list[Any],
|
|
234
|
+
plot_steps: list[Any],
|
|
235
|
+
export_steps: list[Any],
|
|
236
|
+
registry: Any,
|
|
237
|
+
selected_steps: SelectedSteps,
|
|
238
|
+
operation: str,
|
|
239
|
+
reset_records: bool,
|
|
240
|
+
verbose: bool,
|
|
241
|
+
console: Console,
|
|
242
|
+
include_pipeline: bool,
|
|
243
|
+
show_next_steps: bool,
|
|
244
|
+
job_label: str | None,
|
|
245
|
+
) -> ExecutionResult:
|
|
246
|
+
"""Execute one mutating plan while the caller holds the experiment writer lease."""
|
|
247
|
+
|
|
248
|
+
# Repeat the lightweight preflight under the writer lease. The first check
|
|
249
|
+
# prevents ordinary layout mutation; this one closes the race with another
|
|
250
|
+
# process that may have interrupted a reset before we acquired the lease.
|
|
251
|
+
store.validate_no_interrupted_epoch()
|
|
252
|
+
if not reset_records:
|
|
253
|
+
store.ensure_layout()
|
|
254
|
+
if plot_steps:
|
|
255
|
+
ensure_mpl_cache_dir()
|
|
256
|
+
if reset_records:
|
|
257
|
+
close_reader_logger()
|
|
258
|
+
try:
|
|
259
|
+
provenance_epoch_id = store.reset_generated_epoch(
|
|
260
|
+
preserved_paths=(decl.experiment_semantics.layout.subdir_path("notebooks"),)
|
|
261
|
+
)
|
|
262
|
+
except BaseException as reset_failure:
|
|
263
|
+
try:
|
|
264
|
+
configure_logger(out_dir=out_dir, log_level=log_level, verbose=verbose, console=console)
|
|
265
|
+
except BaseException as logger_failure:
|
|
266
|
+
reset_failure.add_note(
|
|
267
|
+
"Reader also could not restore the process logger after the reset failed "
|
|
268
|
+
f"({type(logger_failure).__name__})."
|
|
269
|
+
)
|
|
270
|
+
raise
|
|
271
|
+
else:
|
|
272
|
+
provenance_epoch_id = store.provenance_epoch_id()
|
|
273
|
+
store.bind_provenance_epoch(provenance_epoch_id)
|
|
274
|
+
logger = configure_logger(out_dir=out_dir, log_level=log_level, verbose=verbose, console=console)
|
|
275
|
+
ledger = InvocationLedger(
|
|
276
|
+
experiment_root=decl.experiment.root,
|
|
277
|
+
outputs_dir=out_dir,
|
|
278
|
+
provenance_epoch_id=provenance_epoch_id,
|
|
279
|
+
epoch_guard=store.assert_provenance_epoch,
|
|
280
|
+
writer_lock=store.provenance_lock,
|
|
281
|
+
)
|
|
282
|
+
ctx = build_run_context(
|
|
283
|
+
decl=decl,
|
|
284
|
+
runtime=runtime,
|
|
285
|
+
out_dir=out_dir,
|
|
286
|
+
store=store,
|
|
287
|
+
logger=logger,
|
|
288
|
+
palette_book=resolve_palette_book(decl=decl, steps=all_steps, dry_run=False),
|
|
289
|
+
)
|
|
290
|
+
steps_by_phase = {
|
|
291
|
+
"pipeline": pipeline_steps,
|
|
292
|
+
"plots": plot_steps,
|
|
293
|
+
"exports": export_steps,
|
|
294
|
+
}
|
|
295
|
+
attempt = ledger.append_attempt(
|
|
296
|
+
config_digest=ctx.config_digest,
|
|
297
|
+
build_identity=current_build_identity(),
|
|
298
|
+
operation=operation,
|
|
299
|
+
selected_step_ids=selected_steps.to_dict(),
|
|
300
|
+
declared_inputs=declared_input_projection(
|
|
301
|
+
steps_by_phase=steps_by_phase,
|
|
302
|
+
experiment_root=decl.experiment.root,
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
before_revisions: dict[str, dict] = {}
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
before_revisions = capture_revision_snapshot(store)
|
|
309
|
+
ctx.logger.info(
|
|
310
|
+
"run • pipeline=%d step(s)%s • plots=%d spec(s) • exports=%d spec(s)",
|
|
311
|
+
len(pipeline_steps),
|
|
312
|
+
" (skipped)" if not include_pipeline else "",
|
|
313
|
+
len(plot_steps),
|
|
314
|
+
len(export_steps),
|
|
315
|
+
)
|
|
316
|
+
if not verbose:
|
|
317
|
+
console.print(
|
|
318
|
+
f"[accent]run[/accent] • pipeline={len(pipeline_steps)} step(s){' (skipped)' if not include_pipeline else ''} "
|
|
319
|
+
f"• plots={len(plot_steps)} spec(s) • exports={len(export_steps)} spec(s)"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if pipeline_steps and registry is None:
|
|
323
|
+
raise ConfigError("pipeline execution requires plugin workbench steps")
|
|
324
|
+
if plot_steps and registry is None:
|
|
325
|
+
raise ConfigError("plot execution requires plugin workbench specs")
|
|
326
|
+
if export_steps and registry is None:
|
|
327
|
+
raise ConfigError("export execution requires plugin workbench specs")
|
|
328
|
+
|
|
329
|
+
run_steps(
|
|
330
|
+
items=pipeline_steps,
|
|
331
|
+
phase="pipeline",
|
|
332
|
+
verbose=verbose,
|
|
333
|
+
console=console,
|
|
334
|
+
ctx=ctx,
|
|
335
|
+
store=store,
|
|
336
|
+
registry=registry,
|
|
337
|
+
)
|
|
338
|
+
if plot_steps:
|
|
339
|
+
run_steps(
|
|
340
|
+
items=plot_steps,
|
|
341
|
+
phase="plots",
|
|
342
|
+
verbose=verbose,
|
|
343
|
+
console=console,
|
|
344
|
+
ctx=ctx,
|
|
345
|
+
store=store,
|
|
346
|
+
registry=registry,
|
|
347
|
+
)
|
|
348
|
+
if export_steps:
|
|
349
|
+
run_steps(
|
|
350
|
+
items=export_steps,
|
|
351
|
+
phase="exports",
|
|
352
|
+
verbose=verbose,
|
|
353
|
+
console=console,
|
|
354
|
+
ctx=ctx,
|
|
355
|
+
store=store,
|
|
356
|
+
registry=registry,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
revision_payloads = produced_record_revisions(
|
|
360
|
+
before=before_revisions,
|
|
361
|
+
after=capture_revision_snapshot(store),
|
|
362
|
+
)
|
|
363
|
+
except BaseException as failure:
|
|
364
|
+
try:
|
|
365
|
+
revisions = produced_record_revisions(
|
|
366
|
+
before=before_revisions,
|
|
367
|
+
after=capture_revision_snapshot(store),
|
|
368
|
+
)
|
|
369
|
+
except BaseException:
|
|
370
|
+
revisions = []
|
|
371
|
+
try:
|
|
372
|
+
ledger.append_result(
|
|
373
|
+
attempt,
|
|
374
|
+
exit_status=130 if isinstance(failure, KeyboardInterrupt) else 1,
|
|
375
|
+
produced_record_revisions=revisions,
|
|
376
|
+
failure=failure,
|
|
377
|
+
)
|
|
378
|
+
except BaseException as ledger_failure:
|
|
379
|
+
failure.add_note(
|
|
380
|
+
f"Reader also could not persist the failed invocation result ({type(ledger_failure).__name__})."
|
|
381
|
+
)
|
|
382
|
+
raise
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
ledger.append_result(
|
|
386
|
+
attempt,
|
|
387
|
+
exit_status=0,
|
|
388
|
+
produced_record_revisions=revision_payloads,
|
|
389
|
+
)
|
|
390
|
+
except BaseException as failure:
|
|
391
|
+
raise InvocationFinalizationError(
|
|
392
|
+
"Execution records were committed, but Reader could not confirm the invocation result. "
|
|
393
|
+
"Keep the committed evidence and run reader verify before handoff.",
|
|
394
|
+
invocation_id=attempt.invocation_id,
|
|
395
|
+
produced_record_revisions=tuple(dict(item) for item in revision_payloads),
|
|
396
|
+
) from failure
|
|
397
|
+
|
|
398
|
+
if show_next_steps:
|
|
399
|
+
next_steps = build_next_steps(decl, job_label=job_label, runtime=runtime)
|
|
400
|
+
table = Table(show_header=True, header_style="bold", box=box.ROUNDED, expand=False)
|
|
401
|
+
table.add_column("Command", style="accent")
|
|
402
|
+
table.add_column("What it does")
|
|
403
|
+
for command, description in next_steps:
|
|
404
|
+
table.add_row(command, description)
|
|
405
|
+
console.print(
|
|
406
|
+
Panel(
|
|
407
|
+
table,
|
|
408
|
+
border_style="green",
|
|
409
|
+
box=box.ROUNDED,
|
|
410
|
+
title=f"Records generated in [path]{out_dir}[/path]",
|
|
411
|
+
title_align="left",
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
console.print(Panel.fit(f"✓ Done — outputs in {str(out_dir)}", border_style="green", box=box.ROUNDED))
|
|
416
|
+
return ExecutionResult(
|
|
417
|
+
invocation_id=attempt.invocation_id,
|
|
418
|
+
provenance_epoch_id=provenance_epoch_id,
|
|
419
|
+
operation=operation,
|
|
420
|
+
status="succeeded",
|
|
421
|
+
dry_run=False,
|
|
422
|
+
selected_steps=selected_steps,
|
|
423
|
+
produced_record_revisions=tuple(
|
|
424
|
+
ProducedRecordRevision.from_payload(revision) for revision in revision_payloads
|
|
425
|
+
),
|
|
426
|
+
ledger_path=ledger.path,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def run_job(
|
|
431
|
+
spec_path: Path,
|
|
432
|
+
*,
|
|
433
|
+
resume_from: str | None = None,
|
|
434
|
+
until: str | None = None,
|
|
435
|
+
reset_records: bool = False,
|
|
436
|
+
dry_run: bool = False,
|
|
437
|
+
log_level: str = "INFO",
|
|
438
|
+
verbose: bool = True,
|
|
439
|
+
console: Console | None = None,
|
|
440
|
+
include_pipeline: bool = True,
|
|
441
|
+
include_plots: bool = True,
|
|
442
|
+
include_exports: bool = True,
|
|
443
|
+
job_label: str | None = None,
|
|
444
|
+
show_next_steps: bool = False,
|
|
445
|
+
runtime: ReaderRuntime | None = None,
|
|
446
|
+
) -> ExecutionResult:
|
|
447
|
+
runtime = runtime or builtin_runtime()
|
|
448
|
+
decl = load_workbench_decl(spec_path, protocols=runtime.protocols)
|
|
449
|
+
return run_spec(
|
|
450
|
+
decl,
|
|
451
|
+
resume_from=resume_from,
|
|
452
|
+
until=until,
|
|
453
|
+
reset_records=reset_records,
|
|
454
|
+
dry_run=dry_run,
|
|
455
|
+
log_level=log_level,
|
|
456
|
+
verbose=verbose,
|
|
457
|
+
console=console,
|
|
458
|
+
include_pipeline=include_pipeline,
|
|
459
|
+
include_plots=include_plots,
|
|
460
|
+
include_exports=include_exports,
|
|
461
|
+
job_label=job_label,
|
|
462
|
+
show_next_steps=show_next_steps,
|
|
463
|
+
runtime=runtime,
|
|
464
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import logging
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.logging import RichHandler
|
|
11
|
+
|
|
12
|
+
from reader_workbench.errors import ConfigError
|
|
13
|
+
from reader_workbench.runtime import ReaderRuntime
|
|
14
|
+
from reader_workbench.workbench.context import RunContext
|
|
15
|
+
from reader_workbench.workbench.decl import WorkbenchDecl
|
|
16
|
+
from reader_workbench.workbench.records import RecordStore
|
|
17
|
+
from reader_workbench.workbench.records.identity import digest_json
|
|
18
|
+
|
|
19
|
+
from ._shared import needs_plot_palette
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def normalize_log_level(log_level: str) -> str:
|
|
23
|
+
level_name = str(log_level).upper()
|
|
24
|
+
valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
|
|
25
|
+
if level_name not in valid_levels:
|
|
26
|
+
raise ConfigError(f"Invalid log level {log_level!r}. Choose one of: {sorted(valid_levels)}")
|
|
27
|
+
return level_name
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def close_reader_logger() -> None:
|
|
31
|
+
"""Close Reader's process-global handlers before moving an owned log file."""
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("reader")
|
|
34
|
+
for handler in list(logger.handlers):
|
|
35
|
+
logger.removeHandler(handler)
|
|
36
|
+
with contextlib.suppress(Exception):
|
|
37
|
+
handler.close()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def configure_logger(*, out_dir: Path, log_level: str, verbose: bool, console: Console) -> logging.Logger:
|
|
41
|
+
level_name = normalize_log_level(log_level)
|
|
42
|
+
|
|
43
|
+
close_reader_logger()
|
|
44
|
+
logger = logging.getLogger("reader")
|
|
45
|
+
logger.setLevel(getattr(logging, level_name))
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
file_handler = logging.FileHandler(out_dir / "reader.log", encoding="utf-8")
|
|
49
|
+
except OSError as err:
|
|
50
|
+
raise ConfigError(f"Cannot write reader.log in outputs directory: {out_dir}") from err
|
|
51
|
+
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
|
52
|
+
|
|
53
|
+
rich_handler = RichHandler(
|
|
54
|
+
console=console, markup=True, rich_tracebacks=True, show_level=True, show_time=False, show_path=False
|
|
55
|
+
)
|
|
56
|
+
rich_handler.setLevel(getattr(logging, level_name) if verbose else logging.WARNING)
|
|
57
|
+
|
|
58
|
+
logger.addHandler(file_handler)
|
|
59
|
+
logger.addHandler(rich_handler)
|
|
60
|
+
return logger
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def resolve_palette_book(*, decl: WorkbenchDecl, steps: list[Any], dry_run: bool) -> Any:
|
|
64
|
+
palette = decl.plotting_palette
|
|
65
|
+
if palette is not None:
|
|
66
|
+
if not isinstance(palette, str) or not palette.strip():
|
|
67
|
+
raise ConfigError("plotting.palette must be a non-empty string or null")
|
|
68
|
+
palette = palette.strip()
|
|
69
|
+
|
|
70
|
+
if dry_run or not needs_plot_palette(steps, palette):
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
mod = import_module("reader_workbench.plotting.style")
|
|
75
|
+
except ModuleNotFoundError as err:
|
|
76
|
+
missing = str(err.name or "")
|
|
77
|
+
if missing == "matplotlib" or missing.startswith("matplotlib."):
|
|
78
|
+
raise ConfigError(
|
|
79
|
+
"Plot palettes require matplotlib; install plotting dependencies or set plotting.palette: null."
|
|
80
|
+
) from err
|
|
81
|
+
raise ConfigError(f"Failed to import plot palette support: {err}") from err
|
|
82
|
+
except Exception as err:
|
|
83
|
+
raise ConfigError(f"Failed to initialize plot palette support: {err}") from err
|
|
84
|
+
|
|
85
|
+
palette_book_cls = getattr(mod, "PaletteBook", None)
|
|
86
|
+
available_palettes = getattr(mod, "available_palettes", None)
|
|
87
|
+
if palette_book_cls is None or available_palettes is None:
|
|
88
|
+
raise ConfigError(
|
|
89
|
+
"reader_workbench.plotting.style must expose PaletteBook and available_palettes for plotting.palette support."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if palette not in available_palettes():
|
|
93
|
+
raise ConfigError(
|
|
94
|
+
f"Unknown palette {palette!r}. Available: {available_palettes()} (or set plotting.palette: null)."
|
|
95
|
+
)
|
|
96
|
+
return palette_book_cls(palette)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_run_context(
|
|
100
|
+
*,
|
|
101
|
+
decl: WorkbenchDecl,
|
|
102
|
+
runtime: ReaderRuntime,
|
|
103
|
+
out_dir: Path,
|
|
104
|
+
store: RecordStore,
|
|
105
|
+
logger: logging.Logger,
|
|
106
|
+
palette_book: Any,
|
|
107
|
+
) -> RunContext:
|
|
108
|
+
config_digest = decl.config_digest or digest_json(
|
|
109
|
+
{
|
|
110
|
+
"synthetic_workbench": {
|
|
111
|
+
"experiment_id": decl.experiment.id,
|
|
112
|
+
"root": str(decl.experiment.root),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
return RunContext(
|
|
117
|
+
exp_dir=decl.experiment.root,
|
|
118
|
+
outputs_dir=out_dir,
|
|
119
|
+
artifacts_dir=store.artifacts_dir,
|
|
120
|
+
plots_dir=store.plots_dir,
|
|
121
|
+
exports_dir=store.exports_dir,
|
|
122
|
+
records_path=store.records_path,
|
|
123
|
+
logger=logger,
|
|
124
|
+
palette_book=palette_book,
|
|
125
|
+
experiment=decl.experiment_semantics,
|
|
126
|
+
protocol=runtime.bind_protocol(decl.experiment_semantics.protocol),
|
|
127
|
+
config_digest=config_digest,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def slice_pipeline_steps(steps: list[Any], *, resume_from: str | None, until: str | None) -> list[Any]:
|
|
132
|
+
selected = list(steps)
|
|
133
|
+
start_index = 0
|
|
134
|
+
if resume_from:
|
|
135
|
+
try:
|
|
136
|
+
start_index = next(i for i, step in enumerate(selected) if step.id == resume_from)
|
|
137
|
+
selected = selected[start_index:]
|
|
138
|
+
except StopIteration:
|
|
139
|
+
raise ConfigError(f"--from: step id '{resume_from}' not found") from None
|
|
140
|
+
if until:
|
|
141
|
+
try:
|
|
142
|
+
until_index = next(i for i, step in enumerate(steps) if step.id == until)
|
|
143
|
+
except StopIteration:
|
|
144
|
+
raise ConfigError(f"--until: step id '{until}' not found") from None
|
|
145
|
+
if resume_from and start_index > until_index:
|
|
146
|
+
raise ConfigError(f"--from '{resume_from}' comes after --until '{until}' in pipeline order")
|
|
147
|
+
relative_index = until_index - start_index
|
|
148
|
+
selected = selected[: relative_index + 1]
|
|
149
|
+
return selected
|