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,307 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import tempfile
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import asdict, dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from reader_workbench.errors import ReaderError
|
|
12
|
+
from reader_workbench.runtime import builtin_runtime
|
|
13
|
+
from reader_workbench.workbench.decl import load_workbench_decl
|
|
14
|
+
from reader_workbench.workbench.engine import run_spec
|
|
15
|
+
from reader_workbench.workbench.engine.validation import validation_summary
|
|
16
|
+
from reader_workbench.workbench.experiments import discover_experiment_configs
|
|
17
|
+
from reader_workbench.workbench.graph import resolve_workbench
|
|
18
|
+
from reader_workbench.workbench.inspection.runtime import record_producer_map, workbench_record_verification_scope
|
|
19
|
+
from reader_workbench.workbench.records import verify_record_store
|
|
20
|
+
|
|
21
|
+
from .staging import stage_audit_workspace
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(slots=True)
|
|
25
|
+
class AuditResult:
|
|
26
|
+
config: str
|
|
27
|
+
name: str
|
|
28
|
+
lifecycle: str
|
|
29
|
+
status: str
|
|
30
|
+
phase: str
|
|
31
|
+
seconds: float
|
|
32
|
+
detail: str | None
|
|
33
|
+
expected_plots: int
|
|
34
|
+
expected_exports: int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def discover_year_dirs(root: Path) -> list[str]:
|
|
38
|
+
return sorted(path.name for path in root.iterdir() if path.is_dir() and path.name.isdigit() and len(path.name) == 4)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def discover_configs(root: Path, years: set[str]) -> list[Path]:
|
|
42
|
+
configs = discover_experiment_configs(root, include_scaffolds=False)
|
|
43
|
+
selected: list[Path] = []
|
|
44
|
+
for config in configs:
|
|
45
|
+
try:
|
|
46
|
+
rel = config.relative_to(root)
|
|
47
|
+
except ValueError:
|
|
48
|
+
continue
|
|
49
|
+
if not rel.parts:
|
|
50
|
+
continue
|
|
51
|
+
if years and rel.parts[0] not in years:
|
|
52
|
+
continue
|
|
53
|
+
selected.append(config)
|
|
54
|
+
return sorted(selected)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def verify_outputs(decl, runtime) -> tuple[int, int, str | None]:
|
|
58
|
+
workbench = resolve_workbench(decl)
|
|
59
|
+
verification_scope = workbench_record_verification_scope(workbench, runtime=runtime)
|
|
60
|
+
layout = decl.experiment_semantics.layout
|
|
61
|
+
outputs = layout.outputs_dir
|
|
62
|
+
store = runtime.record_store(
|
|
63
|
+
outputs,
|
|
64
|
+
plots_subdir=layout.plots_subdir,
|
|
65
|
+
exports_subdir=layout.exports_subdir,
|
|
66
|
+
experiment_root=decl.experiment.root,
|
|
67
|
+
create=False,
|
|
68
|
+
)
|
|
69
|
+
latest_ids = {record.record_id for record in store.iter_latest_records()}
|
|
70
|
+
expected_plot_ids = {f"plot:{plot.id}" for plot in workbench.plots}
|
|
71
|
+
expected_export_ids = {f"export:{export.id}" for export in workbench.exports}
|
|
72
|
+
expected_dataframe_ids = set(record_producer_map(workbench.plugin_steps(), runtime=runtime))
|
|
73
|
+
|
|
74
|
+
if (outputs / "manifests" / "records.json").exists() is False:
|
|
75
|
+
return len(expected_plot_ids), len(expected_export_ids), "records.json missing"
|
|
76
|
+
missing_dataframe_ids = sorted(expected_dataframe_ids - latest_ids)
|
|
77
|
+
if missing_dataframe_ids:
|
|
78
|
+
return (
|
|
79
|
+
len(expected_plot_ids),
|
|
80
|
+
len(expected_export_ids),
|
|
81
|
+
f"missing declared dataframe records: {missing_dataframe_ids}",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
missing_plot_ids = sorted(expected_plot_ids - latest_ids)
|
|
85
|
+
if missing_plot_ids:
|
|
86
|
+
return len(expected_plot_ids), len(expected_export_ids), f"missing plot records: {missing_plot_ids}"
|
|
87
|
+
missing_export_ids = sorted(expected_export_ids - latest_ids)
|
|
88
|
+
if missing_export_ids:
|
|
89
|
+
return len(expected_plot_ids), len(expected_export_ids), f"missing export records: {missing_export_ids}"
|
|
90
|
+
|
|
91
|
+
plots_dir = outputs / layout.plots_subdir
|
|
92
|
+
exports_dir = outputs / layout.exports_subdir
|
|
93
|
+
if expected_plot_ids and not any(path.is_file() for path in plots_dir.rglob("*")):
|
|
94
|
+
return len(expected_plot_ids), len(expected_export_ids), "expected plot files were not created"
|
|
95
|
+
if expected_export_ids and not any(path.is_file() for path in exports_dir.rglob("*")):
|
|
96
|
+
return len(expected_plot_ids), len(expected_export_ids), "expected export files were not created"
|
|
97
|
+
|
|
98
|
+
verification = verify_record_store(
|
|
99
|
+
store,
|
|
100
|
+
experiment_root=decl.experiment.root,
|
|
101
|
+
expected_config_digest=decl.config_digest,
|
|
102
|
+
scope=verification_scope,
|
|
103
|
+
)
|
|
104
|
+
if verification["status"] != "ok":
|
|
105
|
+
issues = list(verification.get("issues") or [])
|
|
106
|
+
for record in verification.get("records") or []:
|
|
107
|
+
issues.extend(record.get("issues") or [])
|
|
108
|
+
first = issues[0] if issues else {"code": "verification.failed", "reason": "unknown verification failure"}
|
|
109
|
+
return (
|
|
110
|
+
len(expected_plot_ids),
|
|
111
|
+
len(expected_export_ids),
|
|
112
|
+
f"reader verify failed: {first['code']} • {first['reason']}",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return len(expected_plot_ids), len(expected_export_ids), None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def audit_config(config_path: Path, *, include_non_active: bool, runtime) -> AuditResult:
|
|
119
|
+
start = time.perf_counter()
|
|
120
|
+
try:
|
|
121
|
+
decl = load_workbench_decl(config_path, protocols=runtime.protocols)
|
|
122
|
+
except ReaderError as exc:
|
|
123
|
+
return AuditResult(
|
|
124
|
+
config=str(config_path),
|
|
125
|
+
name=config_path.parent.name,
|
|
126
|
+
lifecycle="unknown",
|
|
127
|
+
status="failed",
|
|
128
|
+
phase="config",
|
|
129
|
+
seconds=time.perf_counter() - start,
|
|
130
|
+
detail=str(exc),
|
|
131
|
+
expected_plots=0,
|
|
132
|
+
expected_exports=0,
|
|
133
|
+
)
|
|
134
|
+
lifecycle = decl.experiment.lifecycle
|
|
135
|
+
rel_config = str(config_path)
|
|
136
|
+
if lifecycle != "active" and not include_non_active:
|
|
137
|
+
return AuditResult(
|
|
138
|
+
config=rel_config,
|
|
139
|
+
name=config_path.parent.name,
|
|
140
|
+
lifecycle=lifecycle,
|
|
141
|
+
status="skipped",
|
|
142
|
+
phase="lifecycle",
|
|
143
|
+
seconds=time.perf_counter() - start,
|
|
144
|
+
detail=f"skipped non-active lifecycle: {lifecycle}",
|
|
145
|
+
expected_plots=0,
|
|
146
|
+
expected_exports=0,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
source_summary = validation_summary(
|
|
150
|
+
decl,
|
|
151
|
+
check_files=True,
|
|
152
|
+
exp_root=decl.experiment.root,
|
|
153
|
+
runtime=runtime,
|
|
154
|
+
)
|
|
155
|
+
if source_summary["status"] != "ok":
|
|
156
|
+
return AuditResult(
|
|
157
|
+
config=rel_config,
|
|
158
|
+
name=config_path.parent.name,
|
|
159
|
+
lifecycle=lifecycle,
|
|
160
|
+
status="failed",
|
|
161
|
+
phase="validate",
|
|
162
|
+
seconds=time.perf_counter() - start,
|
|
163
|
+
detail="; ".join(source_summary["errors"]) or "validation failed",
|
|
164
|
+
expected_plots=0,
|
|
165
|
+
expected_exports=0,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
with tempfile.TemporaryDirectory(prefix="reader-local-audit-") as tmpdir:
|
|
169
|
+
try:
|
|
170
|
+
staged_config = stage_audit_workspace(
|
|
171
|
+
config_path=config_path,
|
|
172
|
+
target_root=Path(tmpdir),
|
|
173
|
+
resources=decl.experiment_semantics.resources,
|
|
174
|
+
runtime=runtime,
|
|
175
|
+
)
|
|
176
|
+
staged_decl = load_workbench_decl(staged_config, protocols=runtime.protocols)
|
|
177
|
+
except (ReaderError, OSError) as exc:
|
|
178
|
+
return AuditResult(
|
|
179
|
+
config=rel_config,
|
|
180
|
+
name=config_path.parent.name,
|
|
181
|
+
lifecycle=lifecycle,
|
|
182
|
+
status="failed",
|
|
183
|
+
phase="validate",
|
|
184
|
+
seconds=time.perf_counter() - start,
|
|
185
|
+
detail=str(exc),
|
|
186
|
+
expected_plots=0,
|
|
187
|
+
expected_exports=0,
|
|
188
|
+
)
|
|
189
|
+
summary = validation_summary(
|
|
190
|
+
staged_decl, check_files=True, exp_root=staged_decl.experiment.root, runtime=runtime
|
|
191
|
+
)
|
|
192
|
+
if summary["status"] != "ok":
|
|
193
|
+
return AuditResult(
|
|
194
|
+
config=rel_config,
|
|
195
|
+
name=config_path.parent.name,
|
|
196
|
+
lifecycle=lifecycle,
|
|
197
|
+
status="failed",
|
|
198
|
+
phase="validate",
|
|
199
|
+
seconds=time.perf_counter() - start,
|
|
200
|
+
detail="; ".join(summary["errors"]) or "validation failed",
|
|
201
|
+
expected_plots=0,
|
|
202
|
+
expected_exports=0,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
run_spec(
|
|
207
|
+
staged_decl,
|
|
208
|
+
include_pipeline=True,
|
|
209
|
+
include_plots=True,
|
|
210
|
+
include_exports=True,
|
|
211
|
+
runtime=runtime,
|
|
212
|
+
console=Console(file=io.StringIO(), force_terminal=False, color_system=None),
|
|
213
|
+
log_level="ERROR",
|
|
214
|
+
verbose=False,
|
|
215
|
+
)
|
|
216
|
+
except Exception as exc: # pragma: no cover - exercised by live audit runs
|
|
217
|
+
return AuditResult(
|
|
218
|
+
config=rel_config,
|
|
219
|
+
name=config_path.parent.name,
|
|
220
|
+
lifecycle=lifecycle,
|
|
221
|
+
status="failed",
|
|
222
|
+
phase="run",
|
|
223
|
+
seconds=time.perf_counter() - start,
|
|
224
|
+
detail=str(exc),
|
|
225
|
+
expected_plots=0,
|
|
226
|
+
expected_exports=0,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
expected_plots, expected_exports, verify_error = verify_outputs(staged_decl, runtime)
|
|
230
|
+
if verify_error is not None:
|
|
231
|
+
return AuditResult(
|
|
232
|
+
config=rel_config,
|
|
233
|
+
name=config_path.parent.name,
|
|
234
|
+
lifecycle=lifecycle,
|
|
235
|
+
status="failed",
|
|
236
|
+
phase="verify",
|
|
237
|
+
seconds=time.perf_counter() - start,
|
|
238
|
+
detail=verify_error,
|
|
239
|
+
expected_plots=expected_plots,
|
|
240
|
+
expected_exports=expected_exports,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return AuditResult(
|
|
244
|
+
config=rel_config,
|
|
245
|
+
name=config_path.parent.name,
|
|
246
|
+
lifecycle=lifecycle,
|
|
247
|
+
status="passed",
|
|
248
|
+
phase="complete",
|
|
249
|
+
seconds=time.perf_counter() - start,
|
|
250
|
+
detail=None,
|
|
251
|
+
expected_plots=expected_plots,
|
|
252
|
+
expected_exports=expected_exports,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def render_text(results: list[AuditResult]) -> str:
|
|
257
|
+
lines = []
|
|
258
|
+
for result in results:
|
|
259
|
+
detail = f" :: {result.detail}" if result.detail else ""
|
|
260
|
+
lines.append(
|
|
261
|
+
f"[{result.status.upper():7}] {result.name} "
|
|
262
|
+
f"(lifecycle={result.lifecycle}, phase={result.phase}, "
|
|
263
|
+
f"plots={result.expected_plots}, exports={result.expected_exports}, "
|
|
264
|
+
f"seconds={result.seconds:.2f}){detail}"
|
|
265
|
+
)
|
|
266
|
+
counts: dict[str, int] = {}
|
|
267
|
+
for result in results:
|
|
268
|
+
counts[result.status] = counts.get(result.status, 0) + 1
|
|
269
|
+
summary = ", ".join(f"{key}={counts[key]}" for key in sorted(counts))
|
|
270
|
+
lines.append(f"Summary: {summary}")
|
|
271
|
+
return "\n".join(lines)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def audit_experiments(
|
|
275
|
+
root: Path,
|
|
276
|
+
*,
|
|
277
|
+
years: tuple[str, ...] | None = None,
|
|
278
|
+
include_non_active: bool = False,
|
|
279
|
+
fail_fast: bool = False,
|
|
280
|
+
) -> dict[str, object]:
|
|
281
|
+
root = root.resolve()
|
|
282
|
+
if not root.exists() or not root.is_dir():
|
|
283
|
+
raise ValueError(f"Experiments root not found: {root}")
|
|
284
|
+
selected_years = discover_year_dirs(root) if years is None else [str(year) for year in years]
|
|
285
|
+
if not selected_years:
|
|
286
|
+
raise ValueError(f"No numeric year directories found under {root}. Use --years to select specific directories.")
|
|
287
|
+
runtime = builtin_runtime()
|
|
288
|
+
configs = discover_configs(root, set(selected_years))
|
|
289
|
+
|
|
290
|
+
results: list[AuditResult] = []
|
|
291
|
+
for config_path in configs:
|
|
292
|
+
result = audit_config(config_path, include_non_active=include_non_active, runtime=runtime)
|
|
293
|
+
results.append(result)
|
|
294
|
+
if fail_fast and result.status == "failed":
|
|
295
|
+
break
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
"root": str(root),
|
|
299
|
+
"years": sorted(selected_years),
|
|
300
|
+
"summary": {
|
|
301
|
+
"experiments": len(results),
|
|
302
|
+
"passed": sum(1 for item in results if item.status == "passed"),
|
|
303
|
+
"failed": sum(1 for item in results if item.status == "failed"),
|
|
304
|
+
"skipped": sum(1 for item in results if item.status == "skipped"),
|
|
305
|
+
},
|
|
306
|
+
"results": [asdict(result) for result in results],
|
|
307
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from reader_workbench.errors import ConfigError, RecordError
|
|
9
|
+
from reader_workbench.runtime import ReaderRuntime
|
|
10
|
+
from reader_workbench.workbench.experiment import ResourceCatalog
|
|
11
|
+
from reader_workbench.workbench.experiments import find_experiments_root
|
|
12
|
+
from reader_workbench.workbench.graph import SourceRecordRef
|
|
13
|
+
from reader_workbench.workbench.records import (
|
|
14
|
+
record_paths,
|
|
15
|
+
record_to_dict,
|
|
16
|
+
verify_record_artifact_integrity,
|
|
17
|
+
)
|
|
18
|
+
from reader_workbench.workbench.records.store import RECORD_CATALOG_SCHEMA_VERSION
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def stage_experiment(source_dir: Path, target_dir: Path) -> Path:
|
|
22
|
+
shutil.copytree(
|
|
23
|
+
source_dir,
|
|
24
|
+
target_dir,
|
|
25
|
+
ignore=shutil.ignore_patterns("outputs", "__pycache__", ".DS_Store"),
|
|
26
|
+
symlinks=True,
|
|
27
|
+
)
|
|
28
|
+
_retarget_internal_symlinks(source_dir=source_dir, target_dir=target_dir)
|
|
29
|
+
return target_dir / "config.yaml"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def stage_audit_workspace(
|
|
33
|
+
*,
|
|
34
|
+
config_path: Path,
|
|
35
|
+
target_root: Path,
|
|
36
|
+
resources: ResourceCatalog,
|
|
37
|
+
runtime: ReaderRuntime,
|
|
38
|
+
) -> Path:
|
|
39
|
+
"""Stage one writable experiment and exact read-only snapshots of its record resources."""
|
|
40
|
+
|
|
41
|
+
record_resources = [resource for resource in resources.by_id.values() if resource.kind == "record"]
|
|
42
|
+
try:
|
|
43
|
+
source_experiments_root = find_experiments_root(config_path.parent)
|
|
44
|
+
except ConfigError:
|
|
45
|
+
if record_resources:
|
|
46
|
+
raise
|
|
47
|
+
source_experiments_root = None
|
|
48
|
+
staged_experiments_root = target_root / "experiments"
|
|
49
|
+
relative_experiment_dir = (
|
|
50
|
+
config_path.parent.resolve().relative_to(source_experiments_root)
|
|
51
|
+
if source_experiments_root is not None
|
|
52
|
+
else Path(config_path.parent.name)
|
|
53
|
+
)
|
|
54
|
+
staged_config = stage_experiment(
|
|
55
|
+
config_path.parent,
|
|
56
|
+
staged_experiments_root / relative_experiment_dir,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
resources_by_provider: dict[tuple[Path, Path], set[str]] = {}
|
|
60
|
+
for resource in record_resources:
|
|
61
|
+
provider_key = (resource.experiment_root.resolve(), resource.outputs_dir.resolve())
|
|
62
|
+
resources_by_provider.setdefault(provider_key, set()).add(resource.record_id)
|
|
63
|
+
if resources_by_provider and source_experiments_root is None:
|
|
64
|
+
raise ConfigError("Cross-experiment record resources require a canonical experiments/ workspace")
|
|
65
|
+
for (provider_root, provider_outputs), record_ids in sorted(
|
|
66
|
+
resources_by_provider.items(),
|
|
67
|
+
key=lambda item: (str(item[0][0]), str(item[0][1])),
|
|
68
|
+
):
|
|
69
|
+
_stage_record_provider(
|
|
70
|
+
provider_root=provider_root,
|
|
71
|
+
provider_outputs=provider_outputs,
|
|
72
|
+
record_ids=record_ids,
|
|
73
|
+
source_experiments_root=source_experiments_root,
|
|
74
|
+
staged_experiments_root=staged_experiments_root,
|
|
75
|
+
staged_target_dir=staged_config.parent,
|
|
76
|
+
runtime=runtime,
|
|
77
|
+
)
|
|
78
|
+
return staged_config
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _stage_record_provider(
|
|
82
|
+
*,
|
|
83
|
+
provider_root: Path,
|
|
84
|
+
provider_outputs: Path,
|
|
85
|
+
record_ids: set[str],
|
|
86
|
+
source_experiments_root: Path,
|
|
87
|
+
staged_experiments_root: Path,
|
|
88
|
+
staged_target_dir: Path,
|
|
89
|
+
runtime: ReaderRuntime,
|
|
90
|
+
) -> None:
|
|
91
|
+
staged_provider_dir = _stage_catalog_config(
|
|
92
|
+
source_root=provider_root,
|
|
93
|
+
source_experiments_root=source_experiments_root,
|
|
94
|
+
staged_experiments_root=staged_experiments_root,
|
|
95
|
+
staged_target_dir=staged_target_dir,
|
|
96
|
+
)
|
|
97
|
+
relative_outputs = provider_outputs.relative_to(provider_root)
|
|
98
|
+
staged_outputs = staged_provider_dir / relative_outputs
|
|
99
|
+
source_store = runtime.record_store(
|
|
100
|
+
provider_outputs,
|
|
101
|
+
experiment_root=provider_root,
|
|
102
|
+
create=False,
|
|
103
|
+
)
|
|
104
|
+
catalog = {
|
|
105
|
+
"schema_version": RECORD_CATALOG_SCHEMA_VERSION,
|
|
106
|
+
"provenance_epoch_id": source_store.provenance_epoch_id(),
|
|
107
|
+
"latest": {},
|
|
108
|
+
"history": {},
|
|
109
|
+
}
|
|
110
|
+
source_records = {record.record_id: record for record in source_store.iter_latest_records()}
|
|
111
|
+
copied_paths: set[Path] = set()
|
|
112
|
+
for record_id in sorted(record_ids):
|
|
113
|
+
record = source_records.get(record_id)
|
|
114
|
+
if record is None:
|
|
115
|
+
raise RecordError(f"Source record {record_id!r} is missing from {provider_root.name!r}")
|
|
116
|
+
verify_record_artifact_integrity(record, outputs_dir=provider_outputs)
|
|
117
|
+
payload = record_to_dict(record, outputs_dir=provider_outputs)
|
|
118
|
+
catalog["latest"][record_id] = payload
|
|
119
|
+
catalog["history"][record_id] = [payload]
|
|
120
|
+
for source_path in record_paths(record):
|
|
121
|
+
relative_path = source_path.resolve(strict=True).relative_to(provider_outputs)
|
|
122
|
+
if relative_path in copied_paths:
|
|
123
|
+
continue
|
|
124
|
+
copied_paths.add(relative_path)
|
|
125
|
+
staged_path = staged_outputs / relative_path
|
|
126
|
+
staged_path.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
shutil.copy2(source_path, staged_path)
|
|
128
|
+
for evidence in record.inputs:
|
|
129
|
+
if not isinstance(evidence.ref, SourceRecordRef):
|
|
130
|
+
continue
|
|
131
|
+
_stage_catalog_config(
|
|
132
|
+
source_root=evidence.ref.experiment_root,
|
|
133
|
+
source_experiments_root=source_experiments_root,
|
|
134
|
+
staged_experiments_root=staged_experiments_root,
|
|
135
|
+
staged_target_dir=staged_target_dir,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
staged_manifests = staged_outputs / "manifests"
|
|
139
|
+
staged_manifests.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
(staged_manifests / "records.json").write_text(
|
|
141
|
+
json.dumps(catalog, indent=2, sort_keys=True) + "\n",
|
|
142
|
+
encoding="utf-8",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _stage_catalog_config(
|
|
147
|
+
*,
|
|
148
|
+
source_root: Path,
|
|
149
|
+
source_experiments_root: Path,
|
|
150
|
+
staged_experiments_root: Path,
|
|
151
|
+
staged_target_dir: Path,
|
|
152
|
+
) -> Path:
|
|
153
|
+
staged_dir = staged_experiments_root / source_root.resolve().relative_to(source_experiments_root)
|
|
154
|
+
if staged_dir == staged_target_dir:
|
|
155
|
+
return staged_dir
|
|
156
|
+
staged_dir.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
staged_config = staged_dir / "config.yaml"
|
|
158
|
+
if not staged_config.exists():
|
|
159
|
+
shutil.copy2(source_root / "config.yaml", staged_config)
|
|
160
|
+
return staged_dir
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _retarget_internal_symlinks(*, source_dir: Path, target_dir: Path) -> None:
|
|
164
|
+
"""Map confined source symlinks into the staged tree without following links while copying."""
|
|
165
|
+
|
|
166
|
+
source_root = source_dir.resolve(strict=True)
|
|
167
|
+
target_root = target_dir.resolve(strict=True)
|
|
168
|
+
for current, dirnames, filenames in os.walk(target_root, followlinks=False):
|
|
169
|
+
current_path = Path(current)
|
|
170
|
+
for name in [*dirnames, *filenames]:
|
|
171
|
+
staged_link = current_path / name
|
|
172
|
+
if not staged_link.is_symlink():
|
|
173
|
+
continue
|
|
174
|
+
relative_link = staged_link.relative_to(target_root)
|
|
175
|
+
source_link = source_root / relative_link
|
|
176
|
+
try:
|
|
177
|
+
source_target = source_link.resolve(strict=True)
|
|
178
|
+
relative_target = source_target.relative_to(source_root)
|
|
179
|
+
except (OSError, RuntimeError, ValueError):
|
|
180
|
+
# External, dangling, or cyclic links remain links. Staged
|
|
181
|
+
# validation can then reject them without copytree traversing them.
|
|
182
|
+
continue
|
|
183
|
+
staged_target = target_root / relative_target
|
|
184
|
+
link_target = os.path.relpath(staged_target, start=staged_link.parent)
|
|
185
|
+
target_is_directory = source_target.is_dir()
|
|
186
|
+
staged_link.unlink()
|
|
187
|
+
staged_link.symlink_to(link_target, target_is_directory=target_is_directory)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import audit as _audit # noqa: F401
|
|
4
|
+
from . import demo as _demo # noqa: F401
|
|
5
|
+
from . import dop as _dop # noqa: F401
|
|
6
|
+
from . import experiments as _experiments # noqa: F401
|
|
7
|
+
from . import maintenance as _maintenance # noqa: F401
|
|
8
|
+
from . import notebooks as notebook_commands
|
|
9
|
+
from . import protocols as _protocols # noqa: F401
|
|
10
|
+
from . import shared
|
|
11
|
+
from . import surfaces as _surfaces # noqa: F401
|
|
12
|
+
from . import verification as _verification # noqa: F401
|
|
13
|
+
from .audit import audit_app
|
|
14
|
+
from .experiments import config, explain, inspect, ls, run, validate
|
|
15
|
+
from .helpers import infer_job_path as _infer_job_path
|
|
16
|
+
from .main import main
|
|
17
|
+
from .maintenance import maintain_app
|
|
18
|
+
from .notebooks import _launch_marimo, notebook
|
|
19
|
+
from .protocols import init, protocols
|
|
20
|
+
from .shared import THEME, app, console, subprocess
|
|
21
|
+
from .surfaces import export, plot, plugins, records, steps
|
|
22
|
+
from .verification import verify
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"THEME",
|
|
26
|
+
"_infer_job_path",
|
|
27
|
+
"_launch_marimo",
|
|
28
|
+
"audit_app",
|
|
29
|
+
"app",
|
|
30
|
+
"config",
|
|
31
|
+
"console",
|
|
32
|
+
"explain",
|
|
33
|
+
"export",
|
|
34
|
+
"init",
|
|
35
|
+
"inspect",
|
|
36
|
+
"ls",
|
|
37
|
+
"maintain_app",
|
|
38
|
+
"main",
|
|
39
|
+
"notebook",
|
|
40
|
+
"notebook_commands",
|
|
41
|
+
"plot",
|
|
42
|
+
"plugins",
|
|
43
|
+
"protocols",
|
|
44
|
+
"records",
|
|
45
|
+
"run",
|
|
46
|
+
"shared",
|
|
47
|
+
"steps",
|
|
48
|
+
"subprocess",
|
|
49
|
+
"validate",
|
|
50
|
+
"verify",
|
|
51
|
+
]
|