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,274 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
8
|
+
|
|
9
|
+
from reader_workbench.errors import ConfigError
|
|
10
|
+
from reader_workbench.workbench.dop import builtin_dop_registry
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ExperimentSpec(BaseModel):
|
|
14
|
+
id: str
|
|
15
|
+
title: str | None = None
|
|
16
|
+
lifecycle: str = "active"
|
|
17
|
+
|
|
18
|
+
model_config = {"extra": "forbid"}
|
|
19
|
+
|
|
20
|
+
@field_validator("id", mode="after")
|
|
21
|
+
@classmethod
|
|
22
|
+
def _validate_id(cls, v: str) -> str:
|
|
23
|
+
if not isinstance(v, str) or not v.strip():
|
|
24
|
+
raise ConfigError("experiment.id must be a non-empty string")
|
|
25
|
+
return v
|
|
26
|
+
|
|
27
|
+
@field_validator("lifecycle", mode="after")
|
|
28
|
+
@classmethod
|
|
29
|
+
def _validate_lifecycle(cls, v: str) -> str:
|
|
30
|
+
if not isinstance(v, str) or not v.strip():
|
|
31
|
+
raise ConfigError("experiment.lifecycle must be a non-empty string")
|
|
32
|
+
lifecycle = v.strip().lower()
|
|
33
|
+
allowed = {"active", "draft", "template"}
|
|
34
|
+
if lifecycle not in allowed:
|
|
35
|
+
raise ConfigError(f"experiment.lifecycle must be one of: {', '.join(sorted(allowed))}")
|
|
36
|
+
return lifecycle
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ReplicateKind = Literal["biological", "technical", "mixed", "unknown", "not_applicable"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EvidenceSpec(BaseModel):
|
|
43
|
+
data_class: str
|
|
44
|
+
data_class_reason: str
|
|
45
|
+
replicate_kind: ReplicateKind
|
|
46
|
+
replicate_identity_field: str | None = None
|
|
47
|
+
|
|
48
|
+
model_config = {"extra": "forbid"}
|
|
49
|
+
|
|
50
|
+
@field_validator("data_class", mode="after")
|
|
51
|
+
@classmethod
|
|
52
|
+
def _validate_data_class(cls, value: str) -> str:
|
|
53
|
+
cleaned = value.strip()
|
|
54
|
+
if not cleaned:
|
|
55
|
+
raise ValueError("evidence.data_class must be a non-empty string")
|
|
56
|
+
builtin_dop_registry().data_class(cleaned)
|
|
57
|
+
return cleaned
|
|
58
|
+
|
|
59
|
+
@field_validator("data_class_reason", mode="after")
|
|
60
|
+
@classmethod
|
|
61
|
+
def _validate_data_class_reason(cls, value: str) -> str:
|
|
62
|
+
cleaned = value.strip()
|
|
63
|
+
if not cleaned:
|
|
64
|
+
raise ValueError("evidence.data_class_reason must be a non-empty string")
|
|
65
|
+
return cleaned
|
|
66
|
+
|
|
67
|
+
@field_validator("replicate_identity_field", mode="after")
|
|
68
|
+
@classmethod
|
|
69
|
+
def _validate_replicate_identity_field(cls, value: str | None) -> str | None:
|
|
70
|
+
if value is None:
|
|
71
|
+
return None
|
|
72
|
+
cleaned = value.strip()
|
|
73
|
+
if not cleaned:
|
|
74
|
+
raise ValueError("evidence.replicate_identity_field must be a non-empty string when provided")
|
|
75
|
+
return cleaned
|
|
76
|
+
|
|
77
|
+
@model_validator(mode="after")
|
|
78
|
+
def _validate_replicate_identity(self) -> EvidenceSpec:
|
|
79
|
+
if self.replicate_kind == "not_applicable" and self.replicate_identity_field is not None:
|
|
80
|
+
raise ValueError("evidence.replicate_identity_field cannot be set when replicate_kind is not_applicable")
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class PathsSpec(BaseModel):
|
|
85
|
+
outputs: str = "./outputs"
|
|
86
|
+
plots: str = "plots"
|
|
87
|
+
exports: str = "exports"
|
|
88
|
+
notebooks: str = "notebooks"
|
|
89
|
+
|
|
90
|
+
model_config = {"extra": "forbid"}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class PlottingSpec(BaseModel):
|
|
94
|
+
palette: str | None = "colorblind"
|
|
95
|
+
|
|
96
|
+
model_config = {"extra": "forbid"}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FileResourceSpec(BaseModel):
|
|
100
|
+
kind: Literal["file"]
|
|
101
|
+
path: str
|
|
102
|
+
|
|
103
|
+
model_config = {"extra": "forbid"}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class RecordResourceSpec(BaseModel):
|
|
107
|
+
kind: Literal["record"]
|
|
108
|
+
experiment: str
|
|
109
|
+
record: str
|
|
110
|
+
|
|
111
|
+
model_config = {"extra": "forbid"}
|
|
112
|
+
|
|
113
|
+
@field_validator("experiment", "record", mode="after")
|
|
114
|
+
@classmethod
|
|
115
|
+
def _validate_nonempty(cls, value: str) -> str:
|
|
116
|
+
if not isinstance(value, str) or not value.strip():
|
|
117
|
+
raise ValueError("record resource experiment and record values must be non-empty strings")
|
|
118
|
+
return value.strip()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
ResourceSpec = Annotated[FileResourceSpec | RecordResourceSpec, Field(discriminator="kind")]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ResourcesSpec(BaseModel):
|
|
125
|
+
by_id: dict[str, ResourceSpec] = Field(default_factory=dict)
|
|
126
|
+
|
|
127
|
+
model_config = {"extra": "forbid"}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class AnnotationLabelSpec(BaseModel):
|
|
131
|
+
source: str
|
|
132
|
+
values: dict[str, str] = Field(default_factory=dict)
|
|
133
|
+
output: str | None = None
|
|
134
|
+
|
|
135
|
+
model_config = {"extra": "forbid"}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class AnnotationOrderSpec(BaseModel):
|
|
139
|
+
column: str
|
|
140
|
+
values: list[str] = Field(default_factory=list)
|
|
141
|
+
|
|
142
|
+
model_config = {"extra": "forbid"}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class AnnotationCollectionSpec(BaseModel):
|
|
146
|
+
column: str
|
|
147
|
+
items: dict[str, list[str]] = Field(default_factory=dict)
|
|
148
|
+
|
|
149
|
+
model_config = {"extra": "forbid"}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class AnnotationOrderedStateSpaceSpec(BaseModel):
|
|
153
|
+
column: str
|
|
154
|
+
state_order: list[str]
|
|
155
|
+
values: dict[str, str]
|
|
156
|
+
case_sensitive: bool = True
|
|
157
|
+
|
|
158
|
+
model_config = {"extra": "forbid"}
|
|
159
|
+
|
|
160
|
+
@field_validator("column", mode="after")
|
|
161
|
+
@classmethod
|
|
162
|
+
def _validate_column(cls, value: str) -> str:
|
|
163
|
+
if not isinstance(value, str) or not value.strip():
|
|
164
|
+
raise ValueError("ordered state-space column must be a non-empty string")
|
|
165
|
+
return value.strip()
|
|
166
|
+
|
|
167
|
+
@model_validator(mode="after")
|
|
168
|
+
def _validate_states(self) -> AnnotationOrderedStateSpaceSpec:
|
|
169
|
+
if not self.state_order:
|
|
170
|
+
raise ValueError("state_order must be a non-empty list")
|
|
171
|
+
state_ids = self.state_order
|
|
172
|
+
if any(not state_id.strip() for state_id in state_ids):
|
|
173
|
+
raise ValueError("state_order must contain non-empty strings")
|
|
174
|
+
if len(set(state_ids)) != len(state_ids):
|
|
175
|
+
raise ValueError("state ids must be unique")
|
|
176
|
+
if set(self.values) != set(state_ids):
|
|
177
|
+
raise ValueError("values must have exactly the ids declared by state_order")
|
|
178
|
+
source_values = [self.values[state_id] for state_id in state_ids]
|
|
179
|
+
if any(not value.strip() for value in source_values):
|
|
180
|
+
raise ValueError("values must map state ids to non-empty strings")
|
|
181
|
+
compared = source_values if self.case_sensitive else [value.strip().casefold() for value in source_values]
|
|
182
|
+
if len(set(compared)) != len(compared):
|
|
183
|
+
sensitivity = "true" if self.case_sensitive else "false"
|
|
184
|
+
raise ValueError(f"source values must be unique under case_sensitive={sensitivity}")
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class AnnotationSpec(BaseModel):
|
|
189
|
+
labels: dict[str, AnnotationLabelSpec] = Field(default_factory=dict)
|
|
190
|
+
orders: dict[str, AnnotationOrderSpec] = Field(default_factory=dict)
|
|
191
|
+
collections: dict[str, AnnotationCollectionSpec] = Field(default_factory=dict)
|
|
192
|
+
ordered_state_spaces: dict[str, AnnotationOrderedStateSpaceSpec] = Field(default_factory=dict)
|
|
193
|
+
|
|
194
|
+
model_config = {"extra": "forbid"}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class PlotOutputsSpec(BaseModel):
|
|
198
|
+
profile: str | None = None
|
|
199
|
+
include: list[str] = Field(default_factory=list)
|
|
200
|
+
exclude: list[str] = Field(default_factory=list)
|
|
201
|
+
views: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
|
202
|
+
|
|
203
|
+
model_config = {"extra": "forbid"}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class ExportOutputsSpec(BaseModel):
|
|
207
|
+
include: list[str] = Field(default_factory=list)
|
|
208
|
+
exclude: list[str] = Field(default_factory=list)
|
|
209
|
+
artifacts: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
|
210
|
+
|
|
211
|
+
model_config = {"extra": "forbid"}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class OutputsSpec(BaseModel):
|
|
215
|
+
plots: PlotOutputsSpec = Field(default_factory=PlotOutputsSpec)
|
|
216
|
+
exports: ExportOutputsSpec = Field(default_factory=ExportOutputsSpec)
|
|
217
|
+
|
|
218
|
+
model_config = {"extra": "forbid"}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ProtocolBindingSpec(BaseModel):
|
|
222
|
+
id: str
|
|
223
|
+
inputs: dict[str, Any] = Field(default_factory=dict)
|
|
224
|
+
analysis: dict[str, Any] = Field(default_factory=dict)
|
|
225
|
+
outputs: OutputsSpec = Field(default_factory=OutputsSpec)
|
|
226
|
+
|
|
227
|
+
model_config = {"extra": "forbid"}
|
|
228
|
+
|
|
229
|
+
@field_validator("id", mode="after")
|
|
230
|
+
@classmethod
|
|
231
|
+
def _validate_id(cls, v: str) -> str:
|
|
232
|
+
if not isinstance(v, str) or not v.strip():
|
|
233
|
+
raise ConfigError("protocol.id must be a non-empty string")
|
|
234
|
+
return v
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class ReaderSpec(BaseModel):
|
|
238
|
+
schema_: str = Field(alias="schema")
|
|
239
|
+
experiment: ExperimentSpec
|
|
240
|
+
protocol: ProtocolBindingSpec
|
|
241
|
+
paths: PathsSpec = Field(default_factory=PathsSpec)
|
|
242
|
+
plotting: PlottingSpec = Field(default_factory=PlottingSpec)
|
|
243
|
+
resources: ResourcesSpec = Field(default_factory=ResourcesSpec)
|
|
244
|
+
annotations: AnnotationSpec = Field(default_factory=AnnotationSpec)
|
|
245
|
+
evidence: EvidenceSpec | None = None
|
|
246
|
+
|
|
247
|
+
model_config = {"extra": "forbid"}
|
|
248
|
+
|
|
249
|
+
@field_validator("schema_", mode="after")
|
|
250
|
+
@classmethod
|
|
251
|
+
def _validate_schema(cls, v: str) -> str:
|
|
252
|
+
if v != "reader/v8":
|
|
253
|
+
raise ConfigError("Config schema must be 'reader/v8'. This repo only supports reader/v8.")
|
|
254
|
+
return v
|
|
255
|
+
|
|
256
|
+
@model_validator(mode="after")
|
|
257
|
+
def _validate_evidence_protocol_candidate(self) -> ReaderSpec:
|
|
258
|
+
if self.evidence is None:
|
|
259
|
+
return self
|
|
260
|
+
|
|
261
|
+
data_class = builtin_dop_registry().data_class(self.evidence.data_class)
|
|
262
|
+
protocol_id = self.protocol.id.strip()
|
|
263
|
+
if protocol_id not in data_class.protocol_candidates:
|
|
264
|
+
valid_candidates = ", ".join(data_class.protocol_candidates)
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"evidence.data_class {data_class.id!r} does not admit protocol.id {protocol_id!r}. "
|
|
267
|
+
f"Valid protocol candidates: {valid_candidates}"
|
|
268
|
+
)
|
|
269
|
+
return self
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def load(cls, path: Path) -> ReaderSpec:
|
|
273
|
+
load_module = import_module("reader_workbench.workbench.config.load")
|
|
274
|
+
return load_module.load_reader_spec(path, cls=cls)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from reader_workbench.plotting.style import PaletteBook
|
|
10
|
+
from reader_workbench.protocols import BoundProtocol
|
|
11
|
+
from reader_workbench.workbench.experiment import ExperimentSemantics
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class RunContext:
|
|
16
|
+
exp_dir: Path
|
|
17
|
+
outputs_dir: Path
|
|
18
|
+
artifacts_dir: Path
|
|
19
|
+
plots_dir: Path
|
|
20
|
+
exports_dir: Path
|
|
21
|
+
records_path: Path
|
|
22
|
+
logger: logging.Logger
|
|
23
|
+
palette_book: PaletteBook | None
|
|
24
|
+
experiment: ExperimentSemantics | None = None
|
|
25
|
+
protocol: BoundProtocol | None = None
|
|
26
|
+
config_digest: str = ""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
_EXPORTS = {
|
|
6
|
+
"build": {"build_workbench_decl", "load_workbench_decl"},
|
|
7
|
+
"model": {
|
|
8
|
+
"ExperimentDecl",
|
|
9
|
+
"FileInputDecl",
|
|
10
|
+
"InputBindingDecl",
|
|
11
|
+
"PipelineDecl",
|
|
12
|
+
"PluginStepDecl",
|
|
13
|
+
"RecipeSourceDecl",
|
|
14
|
+
"RecordCollectionInputDecl",
|
|
15
|
+
"RecordInputDecl",
|
|
16
|
+
"RecordOutputDecl",
|
|
17
|
+
"ResourceInputDecl",
|
|
18
|
+
"SurfaceDecl",
|
|
19
|
+
"WorkbenchDecl",
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
__all__ = tuple(sorted({name for names in _EXPORTS.values() for name in names}))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __getattr__(name: str):
|
|
27
|
+
for module_name, names in _EXPORTS.items():
|
|
28
|
+
if name in names:
|
|
29
|
+
module = importlib.import_module(f"reader_workbench.workbench.decl.{module_name}")
|
|
30
|
+
return getattr(module, name)
|
|
31
|
+
raise AttributeError(name)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from reader_workbench.errors import ConfigError
|
|
6
|
+
from reader_workbench.protocols.model import ProtocolBinding, ProtocolCatalog
|
|
7
|
+
from reader_workbench.workbench.config import (
|
|
8
|
+
FileResourceSpec,
|
|
9
|
+
ProtocolBindingSpec,
|
|
10
|
+
ReaderSpec,
|
|
11
|
+
RecordResourceSpec,
|
|
12
|
+
ResourceSpec,
|
|
13
|
+
reader_spec_digest,
|
|
14
|
+
)
|
|
15
|
+
from reader_workbench.workbench.experiment import (
|
|
16
|
+
AnnotationCollections,
|
|
17
|
+
AnnotationCollectionSpec,
|
|
18
|
+
AnnotationLabels,
|
|
19
|
+
AnnotationLabelSpec,
|
|
20
|
+
AnnotationOrders,
|
|
21
|
+
AnnotationOrderSpec,
|
|
22
|
+
AnnotationSemantics,
|
|
23
|
+
ExperimentEvidence,
|
|
24
|
+
ExperimentSemantics,
|
|
25
|
+
FileResourceEntry,
|
|
26
|
+
OrderedStateSpaces,
|
|
27
|
+
OrderedStateSpaceSpec,
|
|
28
|
+
OutputLayout,
|
|
29
|
+
RecordResourceEntry,
|
|
30
|
+
ResourceCatalog,
|
|
31
|
+
)
|
|
32
|
+
from reader_workbench.workbench.experiments import ExperimentCatalog
|
|
33
|
+
from reader_workbench.workbench.paths import resolve_path_within_root
|
|
34
|
+
|
|
35
|
+
from .model import ExperimentDecl, PipelineDecl, SurfaceDecl, WorkbenchDecl
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_workbench_decl(path: Path, *, protocols: ProtocolCatalog) -> WorkbenchDecl:
|
|
39
|
+
spec = ReaderSpec.load(path)
|
|
40
|
+
return build_workbench_decl(spec, source_path=path, protocols=protocols)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_workbench_decl(
|
|
44
|
+
spec: ReaderSpec,
|
|
45
|
+
*,
|
|
46
|
+
source_path: Path,
|
|
47
|
+
protocols: ProtocolCatalog,
|
|
48
|
+
) -> WorkbenchDecl:
|
|
49
|
+
root = source_path.parent.resolve()
|
|
50
|
+
experiment = ExperimentDecl(
|
|
51
|
+
id=spec.experiment.id,
|
|
52
|
+
title=spec.experiment.title or spec.experiment.id,
|
|
53
|
+
lifecycle=spec.experiment.lifecycle,
|
|
54
|
+
root=root,
|
|
55
|
+
)
|
|
56
|
+
layout = OutputLayout(
|
|
57
|
+
outputs_dir=_resolve_outputs_dir(spec.paths.outputs, root=root),
|
|
58
|
+
plots_subdir=_validate_output_subdir(spec.paths.plots, key="plots"),
|
|
59
|
+
exports_subdir=_validate_output_subdir(spec.paths.exports, key="exports"),
|
|
60
|
+
notebooks_subdir=_validate_output_subdir(spec.paths.notebooks, key="notebooks"),
|
|
61
|
+
)
|
|
62
|
+
resources = _bind_resources(spec.resources.by_id or {}, root=root)
|
|
63
|
+
protocol_binding = _bind_protocol(spec.protocol)
|
|
64
|
+
bound_protocol = protocols.bind(protocol_binding)
|
|
65
|
+
compiled = bound_protocol.compile()
|
|
66
|
+
|
|
67
|
+
experiment_semantics = ExperimentSemantics(
|
|
68
|
+
protocol=protocol_binding,
|
|
69
|
+
annotations=AnnotationSemantics(
|
|
70
|
+
labels=AnnotationLabels(
|
|
71
|
+
by_id={
|
|
72
|
+
key: AnnotationLabelSpec(
|
|
73
|
+
source=value.source,
|
|
74
|
+
values=dict(value.values or {}),
|
|
75
|
+
output=value.output,
|
|
76
|
+
)
|
|
77
|
+
for key, value in (spec.annotations.labels or {}).items()
|
|
78
|
+
}
|
|
79
|
+
),
|
|
80
|
+
orders=AnnotationOrders(
|
|
81
|
+
by_id={
|
|
82
|
+
key: AnnotationOrderSpec(column=value.column, values=list(value.values or []))
|
|
83
|
+
for key, value in (spec.annotations.orders or {}).items()
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
collections=AnnotationCollections(
|
|
87
|
+
by_id={
|
|
88
|
+
key: AnnotationCollectionSpec(
|
|
89
|
+
column=value.column,
|
|
90
|
+
items={item_key: list(item_values) for item_key, item_values in (value.items or {}).items()},
|
|
91
|
+
)
|
|
92
|
+
for key, value in (spec.annotations.collections or {}).items()
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
ordered_state_spaces=OrderedStateSpaces(
|
|
96
|
+
by_id={
|
|
97
|
+
key: OrderedStateSpaceSpec(
|
|
98
|
+
column=value.column,
|
|
99
|
+
state_order=tuple(value.state_order),
|
|
100
|
+
source_values=dict(value.values),
|
|
101
|
+
case_sensitive=bool(value.case_sensitive),
|
|
102
|
+
)
|
|
103
|
+
for key, value in (spec.annotations.ordered_state_spaces or {}).items()
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
resources=resources,
|
|
108
|
+
layout=layout,
|
|
109
|
+
protocol_program=compiled.semantic_program,
|
|
110
|
+
evidence=(
|
|
111
|
+
ExperimentEvidence(
|
|
112
|
+
data_class=spec.evidence.data_class,
|
|
113
|
+
data_class_reason=spec.evidence.data_class_reason,
|
|
114
|
+
replicate_kind=spec.evidence.replicate_kind,
|
|
115
|
+
replicate_identity_field=spec.evidence.replicate_identity_field,
|
|
116
|
+
)
|
|
117
|
+
if spec.evidence is not None
|
|
118
|
+
else None
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return WorkbenchDecl(
|
|
123
|
+
experiment=experiment,
|
|
124
|
+
experiment_semantics=experiment_semantics,
|
|
125
|
+
plotting_palette=spec.plotting.palette if spec.plotting else None,
|
|
126
|
+
pipeline=PipelineDecl(runtime=dict(compiled.runtime or {}), steps=tuple(compiled.pipeline)),
|
|
127
|
+
plots=SurfaceDecl(specs=tuple(compiled.plots)),
|
|
128
|
+
exports=SurfaceDecl(specs=tuple(compiled.exports)),
|
|
129
|
+
config_digest=reader_spec_digest(spec),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _resolve_outputs_dir(raw: str, *, root: Path) -> Path:
|
|
134
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
135
|
+
raise ConfigError("paths.outputs must be a non-empty string path")
|
|
136
|
+
try:
|
|
137
|
+
return resolve_path_within_root(raw, root=root)
|
|
138
|
+
except ValueError as err:
|
|
139
|
+
raise ConfigError("paths.outputs must stay under the experiment root after resolving symlinks.") from err
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _validate_output_subdir(raw: str, *, key: str) -> str:
|
|
143
|
+
if raw is None:
|
|
144
|
+
raise ConfigError(f"paths.{key} must be a string subdirectory (use '.' to flatten).")
|
|
145
|
+
if not isinstance(raw, str):
|
|
146
|
+
raise ConfigError(f"paths.{key} must be a string subdirectory")
|
|
147
|
+
subdir = Path(raw)
|
|
148
|
+
if subdir.is_absolute():
|
|
149
|
+
raise ConfigError(f"paths.{key} must be relative to paths.outputs, not absolute.")
|
|
150
|
+
normalized = Path(".") / subdir
|
|
151
|
+
if any(part == ".." for part in normalized.parts):
|
|
152
|
+
raise ConfigError(f"paths.{key} must stay under paths.outputs and may not escape via '..'.")
|
|
153
|
+
return raw
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _bind_resources(resources: dict[str, ResourceSpec], *, root: Path) -> ResourceCatalog:
|
|
157
|
+
bound: dict[str, FileResourceEntry | RecordResourceEntry] = {}
|
|
158
|
+
experiment_catalog: ExperimentCatalog | None = None
|
|
159
|
+
for resource_id, resource in resources.items():
|
|
160
|
+
if isinstance(resource, FileResourceSpec):
|
|
161
|
+
try:
|
|
162
|
+
path = resolve_path_within_root(resource.path, root=root)
|
|
163
|
+
except ValueError as err:
|
|
164
|
+
raise ConfigError(
|
|
165
|
+
f"resources.{resource_id}.path must stay under the experiment root after resolving symlinks."
|
|
166
|
+
) from err
|
|
167
|
+
bound[resource_id] = FileResourceEntry(kind="file", path=path)
|
|
168
|
+
continue
|
|
169
|
+
if isinstance(resource, RecordResourceSpec):
|
|
170
|
+
experiment_catalog = experiment_catalog or ExperimentCatalog.from_experiment_root(root)
|
|
171
|
+
location = experiment_catalog.resolve(resource.experiment)
|
|
172
|
+
bound[resource_id] = RecordResourceEntry(
|
|
173
|
+
kind="record",
|
|
174
|
+
experiment_id=location.id,
|
|
175
|
+
record_id=resource.record,
|
|
176
|
+
experiment_root=location.root,
|
|
177
|
+
outputs_dir=location.outputs_dir,
|
|
178
|
+
)
|
|
179
|
+
continue
|
|
180
|
+
raise ConfigError(f"resources.{resource_id} has unsupported kind {resource.kind!r}")
|
|
181
|
+
return ResourceCatalog(by_id=bound)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _bind_protocol(spec: ProtocolBindingSpec) -> ProtocolBinding:
|
|
185
|
+
return ProtocolBinding(
|
|
186
|
+
id=spec.id,
|
|
187
|
+
inputs=dict(spec.inputs or {}),
|
|
188
|
+
analysis=dict(spec.analysis or {}),
|
|
189
|
+
outputs=spec.outputs.model_dump(exclude_none=True),
|
|
190
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from reader_workbench.workbench.experiment import ExperimentSemantics
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ExperimentDecl:
|
|
12
|
+
id: str
|
|
13
|
+
title: str
|
|
14
|
+
lifecycle: str
|
|
15
|
+
root: Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class RecordInputDecl:
|
|
20
|
+
record_id: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class FileInputDecl:
|
|
25
|
+
path: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class ResourceInputDecl:
|
|
30
|
+
resource_id: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class RecordCollectionInputDecl:
|
|
35
|
+
resource_ids: tuple[str, ...]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
InputBindingDecl = RecordInputDecl | FileInputDecl | ResourceInputDecl | RecordCollectionInputDecl
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class RecordOutputDecl:
|
|
43
|
+
record_id: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class RecipeSourceDecl:
|
|
48
|
+
recipe: str
|
|
49
|
+
with_: dict[str, Any] = field(default_factory=dict)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class PluginStepDecl:
|
|
54
|
+
id: str
|
|
55
|
+
plugin: str
|
|
56
|
+
reads: dict[str, InputBindingDecl] = field(default_factory=dict)
|
|
57
|
+
writes: dict[str, RecordOutputDecl] = field(default_factory=dict)
|
|
58
|
+
with_: dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
source_recipe: RecipeSourceDecl | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class PipelineDecl:
|
|
64
|
+
runtime: dict[str, Any] = field(default_factory=dict)
|
|
65
|
+
steps: tuple[PluginStepDecl, ...] = ()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class SurfaceDecl:
|
|
70
|
+
specs: tuple[PluginStepDecl, ...] = ()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class WorkbenchDecl:
|
|
75
|
+
experiment: ExperimentDecl
|
|
76
|
+
experiment_semantics: ExperimentSemantics
|
|
77
|
+
plotting_palette: str | None
|
|
78
|
+
pipeline: PipelineDecl
|
|
79
|
+
plots: SurfaceDecl
|
|
80
|
+
exports: SurfaceDecl
|
|
81
|
+
config_digest: str = ""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .builtins import BUILTIN_DATA_CLASSES, BUILTIN_READY_SPECS, DOP_SCHEMA, builtin_dop_registry
|
|
2
|
+
from .model import DataClassSpec, DopRegistry, ReadySpec
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"BUILTIN_DATA_CLASSES",
|
|
6
|
+
"BUILTIN_READY_SPECS",
|
|
7
|
+
"DOP_SCHEMA",
|
|
8
|
+
"DataClassSpec",
|
|
9
|
+
"DopRegistry",
|
|
10
|
+
"ReadySpec",
|
|
11
|
+
"builtin_dop_registry",
|
|
12
|
+
]
|