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,229 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from contextvars import ContextVar, Token
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Protocol, cast
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
SCHEMA = "reader.cli/v1"
|
|
13
|
+
_DEFAULT_META = {"projection": "full", "truncated": False, "continuation": None}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class AutomationRequest:
|
|
18
|
+
json_requested: bool
|
|
19
|
+
command: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_REQUEST: ContextVar[AutomationRequest | None] = ContextVar("reader_cli_automation_request", default=None)
|
|
23
|
+
_DOCUMENT_EMITTED: ContextVar[bool] = ContextVar("reader_cli_json_document_emitted", default=False)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ClickError(Protocol):
|
|
27
|
+
"""The stable behavior Reader needs from a Click-compatible usage error."""
|
|
28
|
+
|
|
29
|
+
exit_code: int
|
|
30
|
+
|
|
31
|
+
def format_message(self) -> str: ...
|
|
32
|
+
|
|
33
|
+
def show(self) -> None: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def request_from_argv(argv: Sequence[str]) -> AutomationRequest:
|
|
37
|
+
args = list(argv)
|
|
38
|
+
json_requested = any(
|
|
39
|
+
(item == "--format" and index + 1 < len(args) and args[index + 1].strip().lower() == "json")
|
|
40
|
+
or item.strip().lower() == "--format=json"
|
|
41
|
+
for index, item in enumerate(args)
|
|
42
|
+
)
|
|
43
|
+
positional = [item for item in args if item and not item.startswith("-")]
|
|
44
|
+
command = positional[0] if positional else "reader"
|
|
45
|
+
return AutomationRequest(json_requested=json_requested, command=command)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def begin_request(argv: Sequence[str]) -> tuple[Token, Token]:
|
|
49
|
+
request_token = _REQUEST.set(request_from_argv(argv))
|
|
50
|
+
emitted_token = _DOCUMENT_EMITTED.set(False)
|
|
51
|
+
return request_token, emitted_token
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def end_request(tokens: tuple[Token, Token]) -> None:
|
|
55
|
+
request_token, emitted_token = tokens
|
|
56
|
+
_DOCUMENT_EMITTED.reset(emitted_token)
|
|
57
|
+
_REQUEST.reset(request_token)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _click_context() -> click.Context | None:
|
|
61
|
+
try:
|
|
62
|
+
return click.get_current_context(silent=True)
|
|
63
|
+
except RuntimeError:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def json_requested() -> bool:
|
|
68
|
+
request = _REQUEST.get()
|
|
69
|
+
if request is not None:
|
|
70
|
+
return request.json_requested
|
|
71
|
+
context = _click_context()
|
|
72
|
+
while context is not None:
|
|
73
|
+
value = context.params.get("format")
|
|
74
|
+
if isinstance(value, str) and value.strip().lower() == "json":
|
|
75
|
+
return True
|
|
76
|
+
context = context.parent
|
|
77
|
+
return request_from_argv(sys.argv[1:]).json_requested
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def command_name() -> str:
|
|
81
|
+
context = _click_context()
|
|
82
|
+
if context is not None:
|
|
83
|
+
parts = context.command_path.split()
|
|
84
|
+
if len(parts) > 1:
|
|
85
|
+
return " ".join(parts[1:])
|
|
86
|
+
if context.info_name:
|
|
87
|
+
return context.info_name
|
|
88
|
+
request = _REQUEST.get()
|
|
89
|
+
if request is not None:
|
|
90
|
+
return request.command
|
|
91
|
+
return request_from_argv(sys.argv[1:]).command
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def success_envelope(
|
|
95
|
+
data: object,
|
|
96
|
+
*,
|
|
97
|
+
command: str | None = None,
|
|
98
|
+
projection: str = "full",
|
|
99
|
+
truncated: bool = False,
|
|
100
|
+
continuation: str | None = None,
|
|
101
|
+
) -> dict[str, object]:
|
|
102
|
+
return {
|
|
103
|
+
"schema": SCHEMA,
|
|
104
|
+
"ok": True,
|
|
105
|
+
"command": command or command_name(),
|
|
106
|
+
"data": data,
|
|
107
|
+
"error": None,
|
|
108
|
+
"meta": {
|
|
109
|
+
"projection": projection,
|
|
110
|
+
"truncated": truncated,
|
|
111
|
+
"continuation": continuation,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def error_envelope(
|
|
117
|
+
*,
|
|
118
|
+
code: str,
|
|
119
|
+
field: str,
|
|
120
|
+
reason: str,
|
|
121
|
+
remediation: str,
|
|
122
|
+
retryable: bool,
|
|
123
|
+
command: str | None = None,
|
|
124
|
+
) -> dict[str, object]:
|
|
125
|
+
return {
|
|
126
|
+
"schema": SCHEMA,
|
|
127
|
+
"ok": False,
|
|
128
|
+
"command": command or command_name(),
|
|
129
|
+
"data": None,
|
|
130
|
+
"error": {
|
|
131
|
+
"code": code,
|
|
132
|
+
"field": field,
|
|
133
|
+
"reason": reason,
|
|
134
|
+
"remediation": remediation,
|
|
135
|
+
"retryable": retryable,
|
|
136
|
+
},
|
|
137
|
+
"meta": dict(_DEFAULT_META),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def emit_document(payload: dict[str, object]) -> None:
|
|
142
|
+
managed_request = _REQUEST.get() is not None
|
|
143
|
+
if managed_request and _DOCUMENT_EMITTED.get():
|
|
144
|
+
return
|
|
145
|
+
click.echo(json.dumps(payload, indent=2, sort_keys=True), err=False)
|
|
146
|
+
if managed_request:
|
|
147
|
+
_DOCUMENT_EMITTED.set(True)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _has_typer_click_base(exc: Exception, name: str) -> bool:
|
|
151
|
+
return any(base.__name__ == name and base.__module__.partition(".")[0] == "typer" for base in type(exc).__mro__)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def as_click_error(exc: Exception) -> ClickError | None:
|
|
155
|
+
"""Adapt external Click and Typer-owned Click errors at the CLI boundary.
|
|
156
|
+
|
|
157
|
+
Newer Typer releases may own their Click implementation, so exception class
|
|
158
|
+
identity is not guaranteed to match the separately installed ``click``
|
|
159
|
+
package. Reader depends only on the public usage-error behavior here and
|
|
160
|
+
does not import Typer's private compatibility modules.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
if not isinstance(exc, click.ClickException) and not _has_typer_click_base(exc, "ClickException"):
|
|
164
|
+
return None
|
|
165
|
+
if type(getattr(exc, "exit_code", None)) is not int:
|
|
166
|
+
return None
|
|
167
|
+
if not callable(getattr(exc, "format_message", None)) or not callable(getattr(exc, "show", None)):
|
|
168
|
+
return None
|
|
169
|
+
return cast(ClickError, exc)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _bad_parameter_field(exc: ClickError) -> str:
|
|
173
|
+
param = getattr(exc, "param", None)
|
|
174
|
+
if param is not None and getattr(param, "name", None):
|
|
175
|
+
return str(param.name)
|
|
176
|
+
param_hint = getattr(exc, "param_hint", None)
|
|
177
|
+
if param_hint:
|
|
178
|
+
hint = param_hint[0] if isinstance(param_hint, tuple) else param_hint
|
|
179
|
+
return str(hint).lstrip("-").replace("-", "_")
|
|
180
|
+
reason = str(exc).lower()
|
|
181
|
+
if "experiment" in reason and "root" in reason:
|
|
182
|
+
return "root"
|
|
183
|
+
if "protocol" in reason:
|
|
184
|
+
return "name"
|
|
185
|
+
if "log level" in reason:
|
|
186
|
+
return "log_level"
|
|
187
|
+
if "format" in reason:
|
|
188
|
+
return "format"
|
|
189
|
+
if "section" in reason:
|
|
190
|
+
return "section"
|
|
191
|
+
if "continuation" in reason:
|
|
192
|
+
return "continuation"
|
|
193
|
+
if "limit" in reason:
|
|
194
|
+
return "limit"
|
|
195
|
+
return "parameter"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def click_error_details(exc: ClickError) -> dict[str, Any]:
|
|
199
|
+
if isinstance(exc, click.BadParameter) or (
|
|
200
|
+
isinstance(exc, Exception) and _has_typer_click_base(exc, "BadParameter")
|
|
201
|
+
):
|
|
202
|
+
field = _bad_parameter_field(exc)
|
|
203
|
+
return {
|
|
204
|
+
"code": "invalid_parameter",
|
|
205
|
+
"field": field,
|
|
206
|
+
"reason": exc.format_message(),
|
|
207
|
+
"remediation": f"Correct '{field}' and retry the command.",
|
|
208
|
+
"retryable": False,
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
"code": "usage_error",
|
|
212
|
+
"field": "arguments",
|
|
213
|
+
"reason": exc.format_message(),
|
|
214
|
+
"remediation": "Correct the command arguments and retry. Use --help to inspect the accepted interface.",
|
|
215
|
+
"retryable": False,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def reader_error_details(exc: Exception) -> dict[str, Any]:
|
|
220
|
+
reason = str(exc)
|
|
221
|
+
lowered = reason.lower()
|
|
222
|
+
field = "log_level" if "log level" in lowered else "experiment"
|
|
223
|
+
return {
|
|
224
|
+
"code": "reader_error",
|
|
225
|
+
"field": field,
|
|
226
|
+
"reason": reason,
|
|
227
|
+
"remediation": "Correct the reported Reader input or state, then retry the command.",
|
|
228
|
+
"retryable": False,
|
|
229
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rich import box
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
from . import shared
|
|
7
|
+
from .shared import app, table
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command(help="Show a quick guided walkthrough.")
|
|
11
|
+
def demo():
|
|
12
|
+
steps = [
|
|
13
|
+
("1", "Find experiments", "reader ls --details --readiness"),
|
|
14
|
+
("2", "Show experiment details", "reader inspect ./experiments/<experiment>/config.yaml"),
|
|
15
|
+
("3", "List protocols", "reader protocols"),
|
|
16
|
+
(
|
|
17
|
+
"4",
|
|
18
|
+
"Scaffold a new experiment",
|
|
19
|
+
"reader init ./experiments/my_experiment --protocol <protocol-id>",
|
|
20
|
+
),
|
|
21
|
+
("5", "Starter YAML for a protocol", "reader protocols <protocol-id> --example-config"),
|
|
22
|
+
("6", "Show pipeline chain", "reader steps ./experiments/<experiment>/config.yaml"),
|
|
23
|
+
("7", "Explain plan", "reader explain ./experiments/<experiment>/config.yaml"),
|
|
24
|
+
("8", "Validate config + inputs", "reader validate ./experiments/<experiment>/config.yaml"),
|
|
25
|
+
("9", "Run pipeline (records)", "reader run ./experiments/<experiment>/config.yaml"),
|
|
26
|
+
("10", "See records", "reader records ./experiments/<experiment>/config.yaml"),
|
|
27
|
+
("11", "List plot specs", "reader plot ./experiments/<experiment>/config.yaml --list"),
|
|
28
|
+
("12", "Save plots", "reader plot ./experiments/<experiment>/config.yaml"),
|
|
29
|
+
("13", "Run exports", "reader export ./experiments/<experiment>/config.yaml"),
|
|
30
|
+
("14", "Notebook (Marimo)", "reader notebook ./experiments/<experiment>/config.yaml"),
|
|
31
|
+
("15", "Verify records and provenance", "reader verify ./experiments/<experiment>/config.yaml"),
|
|
32
|
+
]
|
|
33
|
+
listing = table("Reader Demo")
|
|
34
|
+
listing.add_column("#", justify="right", style="muted")
|
|
35
|
+
listing.add_column("Goal", style="accent")
|
|
36
|
+
listing.add_column("Command", style="path")
|
|
37
|
+
for row in steps:
|
|
38
|
+
listing.add_row(*row)
|
|
39
|
+
shared.console.print(
|
|
40
|
+
Panel(
|
|
41
|
+
listing,
|
|
42
|
+
border_style="accent",
|
|
43
|
+
box=box.ROUNDED,
|
|
44
|
+
subtitle="[muted]Tip: replace the index with a path or experiment directory[/muted]",
|
|
45
|
+
)
|
|
46
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich import box
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
|
|
7
|
+
from reader_workbench.errors import ConfigError
|
|
8
|
+
|
|
9
|
+
from . import shared
|
|
10
|
+
from ._lazy import load as _load
|
|
11
|
+
from .shared import app, emit_json, normalize_output_format
|
|
12
|
+
|
|
13
|
+
dop_app = typer.Typer(
|
|
14
|
+
add_completion=False,
|
|
15
|
+
help="Inspect the reader-local Data Operations Plan registry.",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _registry():
|
|
20
|
+
return _load("reader_workbench.workbench.dop").builtin_dop_registry()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _validate_registry_protocol_refs(registry, runtime) -> None:
|
|
24
|
+
registry.validate_protocol_refs(descriptor.protocol for descriptor in runtime.protocols.all())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dop_app.command("classes", help="List DOP data classes and their reader protocol candidates.")
|
|
28
|
+
def data_classes(
|
|
29
|
+
name: str | None = typer.Argument(None, metavar="[DATA_CLASS]", help="Optional DOP data class id to describe."),
|
|
30
|
+
protocol: str | None = typer.Option(
|
|
31
|
+
None,
|
|
32
|
+
"--protocol",
|
|
33
|
+
metavar="ID",
|
|
34
|
+
help="Only show data classes that include the given reader protocol id.",
|
|
35
|
+
),
|
|
36
|
+
format: str = typer.Option(
|
|
37
|
+
"table",
|
|
38
|
+
"--format",
|
|
39
|
+
metavar="FMT",
|
|
40
|
+
help="Output format: table | json (default: table).",
|
|
41
|
+
),
|
|
42
|
+
):
|
|
43
|
+
registry = _registry()
|
|
44
|
+
runtime = _load("reader_workbench.runtime").builtin_runtime()
|
|
45
|
+
inspection_dop = _load("reader_workbench.workbench.inspection.dop")
|
|
46
|
+
fmt = normalize_output_format(format)
|
|
47
|
+
try:
|
|
48
|
+
_validate_registry_protocol_refs(registry, runtime)
|
|
49
|
+
selected = registry.data_classes()
|
|
50
|
+
if name is not None:
|
|
51
|
+
selected = (registry.data_class(name),)
|
|
52
|
+
if protocol is not None and protocol.strip():
|
|
53
|
+
resolved_protocol = runtime.protocols.resolve(protocol.strip()).protocol
|
|
54
|
+
selected = tuple(item for item in selected if resolved_protocol in item.protocol_candidates)
|
|
55
|
+
except (ConfigError, ValueError) as err:
|
|
56
|
+
raise typer.BadParameter(str(err)) from err
|
|
57
|
+
if fmt == "json":
|
|
58
|
+
emit_json(inspection_dop.data_classes_payload(selected))
|
|
59
|
+
return
|
|
60
|
+
shared.console.print(Panel(inspection_dop.data_classes_table(selected), border_style="accent", box=box.ROUNDED))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dop_app.command("ready-specs", help="List DOP readiness gates and their reader evidence requirements.")
|
|
64
|
+
def ready_specs(
|
|
65
|
+
name: str | None = typer.Argument(None, metavar="[READY_SPEC]", help="Optional DOP ready spec id to describe."),
|
|
66
|
+
format: str = typer.Option(
|
|
67
|
+
"table",
|
|
68
|
+
"--format",
|
|
69
|
+
metavar="FMT",
|
|
70
|
+
help="Output format: table | json (default: table).",
|
|
71
|
+
),
|
|
72
|
+
):
|
|
73
|
+
registry = _registry()
|
|
74
|
+
inspection_dop = _load("reader_workbench.workbench.inspection.dop")
|
|
75
|
+
readiness = _load("reader_workbench.workbench.inspection.readiness")
|
|
76
|
+
fmt = normalize_output_format(format)
|
|
77
|
+
try:
|
|
78
|
+
registry.validate_ready_refs(
|
|
79
|
+
readiness_states=readiness.READINESS_STATES,
|
|
80
|
+
capability_keys=readiness.READINESS_CAPABILITY_KEYS,
|
|
81
|
+
)
|
|
82
|
+
selected = registry.ready_specs() if name is None else (registry.ready_spec(name),)
|
|
83
|
+
except ValueError as err:
|
|
84
|
+
raise typer.BadParameter(str(err)) from err
|
|
85
|
+
if fmt == "json":
|
|
86
|
+
emit_json(inspection_dop.ready_specs_payload(selected))
|
|
87
|
+
return
|
|
88
|
+
shared.console.print(Panel(inspection_dop.ready_specs_table(selected), border_style="accent", box=box.ROUNDED))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
app.add_typer(dop_app, name="dop")
|