kuration-sdk 0.1.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.
- kuration/__init__.py +16 -0
- kuration/core/__init__.py +180 -0
- kuration/core/_pipeline_support.py +182 -0
- kuration/core/_registration.py +15 -0
- kuration/core/adapters/__init__.py +18 -0
- kuration/core/adapters/base.py +248 -0
- kuration/core/adapters/curated.py +208 -0
- kuration/core/adapters/hdf5.py +115 -0
- kuration/core/adapters/huggingface.py +171 -0
- kuration/core/adapters/jsonl.py +47 -0
- kuration/core/adapters/npz.py +89 -0
- kuration/core/adapters/parquet_dataset.py +137 -0
- kuration/core/adapters/timeseries.py +220 -0
- kuration/core/adapters/zarr_store.py +129 -0
- kuration/core/analyzers.py +540 -0
- kuration/core/binning.py +63 -0
- kuration/core/bounded.py +362 -0
- kuration/core/buffer.py +134 -0
- kuration/core/builtin_operators.py +358 -0
- kuration/core/checkpoints.py +419 -0
- kuration/core/cli/__init__.py +1 -0
- kuration/core/cli/_common.py +277 -0
- kuration/core/cli/check.py +124 -0
- kuration/core/cli/checkpoint.py +534 -0
- kuration/core/cli/curate.py +147 -0
- kuration/core/cli/diff.py +47 -0
- kuration/core/cli/discovery.py +65 -0
- kuration/core/cli/experiment.py +100 -0
- kuration/core/cli/explain.py +135 -0
- kuration/core/cli/export.py +335 -0
- kuration/core/cli/import_dataset.py +103 -0
- kuration/core/cli/infer_schema_cli.py +122 -0
- kuration/core/cli/init_job.py +148 -0
- kuration/core/cli/inspect.py +77 -0
- kuration/core/cli/job.py +218 -0
- kuration/core/cli/main.py +179 -0
- kuration/core/cli/migrate.py +38 -0
- kuration/core/cli/plugin_check.py +39 -0
- kuration/core/cli/preview.py +133 -0
- kuration/core/cli/report.py +111 -0
- kuration/core/cli/stats.py +80 -0
- kuration/core/cli/validate.py +123 -0
- kuration/core/columnar.py +232 -0
- kuration/core/curate.py +120 -0
- kuration/core/dsl/__init__.py +5 -0
- kuration/core/dsl/grammar.py +597 -0
- kuration/core/dsl/registry.py +42 -0
- kuration/core/execution.py +119 -0
- kuration/core/experiment.py +246 -0
- kuration/core/external.py +622 -0
- kuration/core/features.py +86 -0
- kuration/core/html_report.py +46 -0
- kuration/core/importing.py +971 -0
- kuration/core/infer.py +388 -0
- kuration/core/inspection.py +210 -0
- kuration/core/io.py +589 -0
- kuration/core/jsonutil.py +57 -0
- kuration/core/loaders.py +918 -0
- kuration/core/manifest.py +627 -0
- kuration/core/media.py +447 -0
- kuration/core/methods.py +414 -0
- kuration/core/output/__init__.py +21 -0
- kuration/core/output/base.py +71 -0
- kuration/core/output/containers.py +176 -0
- kuration/core/output/dataset.py +694 -0
- kuration/core/output/external_fill.py +318 -0
- kuration/core/output/fill.py +217 -0
- kuration/core/output/presets.py +119 -0
- kuration/core/output/shardwriter.py +269 -0
- kuration/core/output/spec.py +268 -0
- kuration/core/output/stream.py +119 -0
- kuration/core/output/track.py +53 -0
- kuration/core/pipeline.py +2492 -0
- kuration/core/planning.py +410 -0
- kuration/core/plugin_validation.py +211 -0
- kuration/core/plugins.py +332 -0
- kuration/core/progress.py +144 -0
- kuration/core/provenance.py +495 -0
- kuration/core/quality.py +241 -0
- kuration/core/registry.py +623 -0
- kuration/core/report.py +212 -0
- kuration/core/results.py +363 -0
- kuration/core/review.py +389 -0
- kuration/core/schema.py +394 -0
- kuration/core/schemas/job.schema.json +224 -0
- kuration/core/scorers.py +260 -0
- kuration/core/scoring.py +394 -0
- kuration/core/segment.py +145 -0
- kuration/core/select.py +200 -0
- kuration/core/stats.py +1308 -0
- kuration/core/strategy.py +593 -0
- kuration/core/tags.py +54 -0
- kuration/core/torch.py +67 -0
- kuration/core/transforms.py +700 -0
- kuration/core/versioning.py +203 -0
- kuration/core/work_paths.py +119 -0
- kuration/gameplay/__init__.py +94 -0
- kuration/gameplay/_stats.py +486 -0
- kuration/gameplay/action_state_consistency/__init__.py +279 -0
- kuration/gameplay/action_state_consistency/action_state.py +554 -0
- kuration/gameplay/action_state_consistency/anomaly_checker.py +654 -0
- kuration/gameplay/collision/__init__.py +168 -0
- kuration/gameplay/collision/asg_adapter.py +129 -0
- kuration/gameplay/collision/collision_operator.py +400 -0
- kuration/gameplay/collision/directional_collision_analyzer.py +581 -0
- kuration/gameplay/collision/kinematics.py +85 -0
- kuration/gameplay/collision/mesh_cache.py +682 -0
- kuration/gameplay/collision/physics.py +107 -0
- kuration/gameplay/collision/ramp_fitting.py +148 -0
- kuration/gameplay/collision/teapearce_adapter.py +187 -0
- kuration/gameplay/convenience.py +102 -0
- kuration/gameplay/pixel_shift/__init__.py +87 -0
- kuration/gameplay/pixel_shift/diamond_action_decode.py +625 -0
- kuration/gameplay/pixel_shift/pixel_shift_operator.py +638 -0
- kuration/gameplay/slam/__init__.py +243 -0
- kuration/gameplay/slam/_paths.py +26 -0
- kuration/gameplay/slam/_runtime.py +1022 -0
- kuration/gameplay/slam/_vendor/__init__.py +1 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/LICENSE +201 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/NOTICE +20 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/__init__.py +0 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/api.py +455 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/cfg.py +149 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-base.yaml +45 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-giant.yaml +71 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-large.yaml +45 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3-small.yaml +45 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3metric-large.yaml +28 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3mono-large.yaml +28 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/configs/da3nested-giant-large.yaml +10 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/__init__.py +20 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/cam_dec.py +45 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/cam_enc.py +80 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/da3.py +445 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/__init__.py +0 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/dinov2.py +64 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/__init__.py +25 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/attention.py +100 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/block.py +143 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/drop_path.py +35 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/layer_scale.py +31 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/mlp.py +40 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/patch_embed.py +94 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/rope.py +200 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/layers/swiglu_ffn.py +62 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dinov2/vision_transformer.py +458 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dpt.py +458 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/dualdpt.py +488 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/reference_view_selector.py +223 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/__init__.py +0 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/attention.py +109 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/block.py +81 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/head_utils.py +230 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/model/utils/transform.py +208 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/registry.py +49 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/specs.py +45 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/__init__.py +0 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/alignment.py +163 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/constants.py +270 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/export.py +20 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/geometry.py +498 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/io/__init__.py +0 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/io/input_processor.py +501 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/io/output_processor.py +172 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/logger.py +82 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/parallel_utils.py +133 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/pose_align.py +347 -0
- kuration/gameplay/slam/_vendor/depth_anything_3/utils/ray_utils.py +524 -0
- kuration/gameplay/slam/adapter.py +181 -0
- kuration/gameplay/slam/cli.py +317 -0
- kuration/gameplay/slam/demo.py +84 -0
- kuration/gameplay/slam/export.py +420 -0
- kuration/gameplay/slam/motion.py +132 -0
- kuration/gameplay/slam/pointcloud.py +273 -0
- kuration/gameplay/slam/preflight.py +270 -0
- kuration/gameplay/slam/quality.py +227 -0
- kuration/gameplay/slam/runner.py +697 -0
- kuration/gameplay/slam/schema.py +52 -0
- kuration/gameplay/slam/viz.py +221 -0
- kuration/gameplay/slam/workflow.py +143 -0
- kuration/gameplay/video.py +325 -0
- kuration/py.typed +0 -0
- kuration/sample_data/__init__.py +19 -0
- kuration/sample_data/sessions/session-0.parquet +0 -0
- kuration/sample_data/sessions/session-1.parquet +0 -0
- kuration/sample_data/sessions/session-2.parquet +0 -0
- kuration/sample_data/sessions/session-3.parquet +0 -0
- kuration/sample_data/sessions/session-4.parquet +0 -0
- kuration/sample_data/sessions/session-5.parquet +0 -0
- kuration/selection/__init__.py +88 -0
- kuration/selection/operations.py +253 -0
- kuration/selection/operators.py +80 -0
- kuration/selection/plan.py +186 -0
- kuration_sdk-0.1.0.dist-info/METADATA +629 -0
- kuration_sdk-0.1.0.dist-info/RECORD +199 -0
- kuration_sdk-0.1.0.dist-info/WHEEL +5 -0
- kuration_sdk-0.1.0.dist-info/entry_points.txt +26 -0
- kuration_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
- kuration_sdk-0.1.0.dist-info/top_level.txt +1 -0
kuration/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Kuration's public package surface.
|
|
2
|
+
|
|
3
|
+
Implementation lives in explicit product domains:
|
|
4
|
+
|
|
5
|
+
- :mod:`kuration.core` contains the dataset-agnostic curation SDK.
|
|
6
|
+
- :mod:`kuration.gameplay` contains maintained gameplay capabilities.
|
|
7
|
+
- :mod:`kuration.selection` contains built-in dataset-selection recipes
|
|
8
|
+
(balanced coverage, rare-event weighting, cluster-balanced diversity).
|
|
9
|
+
|
|
10
|
+
Core symbols remain available from :mod:`kuration` for a concise default API, while
|
|
11
|
+
domain-specific code is imported from its domain package.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .core import * # noqa: F403
|
|
16
|
+
from .core import __all__ as __all__
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Kuration's dataset-agnostic curation core.
|
|
2
|
+
|
|
3
|
+
Full pipeline: schema-driven metric DSL, input adapters, segmentation, feature engine,
|
|
4
|
+
binning, declarative strategies (base + feature-space + filter + pre-pass filters + a
|
|
5
|
+
learned-scorer seam), the output layer (declared modalities, npz/json/hdf5/parquet shards,
|
|
6
|
+
hours-budget fill, sub-track spans), tag-coverage, experiment + report, parallel /
|
|
7
|
+
skip-and-log / resume / out-of-core execution, path-agnostic local + cloud I/O, schema
|
|
8
|
+
inference, and an `input(...).operator(...)` pipeline API.
|
|
9
|
+
This package contains no gameplay-specific implementation. See README.md and docs/.
|
|
10
|
+
"""
|
|
11
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
__version__ = version("kuration-sdk")
|
|
15
|
+
except PackageNotFoundError: # source tree imported before installation
|
|
16
|
+
__version__ = "0+unknown"
|
|
17
|
+
|
|
18
|
+
from .adapters import (
|
|
19
|
+
CuratedDatasetAdapter,
|
|
20
|
+
HDF5Adapter,
|
|
21
|
+
HuggingFaceAdapter,
|
|
22
|
+
InputAdapter,
|
|
23
|
+
JsonlAdapter,
|
|
24
|
+
NpzAdapter,
|
|
25
|
+
ParquetDatasetAdapter,
|
|
26
|
+
TimeseriesAdapter,
|
|
27
|
+
ZarrAdapter,
|
|
28
|
+
VideoAdapter,
|
|
29
|
+
JoinedMediaAdapter,
|
|
30
|
+
detect_adapter,
|
|
31
|
+
get_adapter,
|
|
32
|
+
register_adapter,
|
|
33
|
+
schema_from_output,
|
|
34
|
+
)
|
|
35
|
+
from .binning import bin_segments, quantile_bins
|
|
36
|
+
from .buffer import Tick, WindowBuffer, build_window, empty_like
|
|
37
|
+
from .columnar import IDENTITY_COLUMNS, SegmentBatch, iter_segment_batches
|
|
38
|
+
from .checkpoints import (CheckpointIdentity, PartitionCommit, StageCheckpoint,
|
|
39
|
+
canonical_hash)
|
|
40
|
+
from .bounded import FeatureStageResult, checkpoint_feature_stage
|
|
41
|
+
from .external import (external_quantile_edges, external_top_k, external_uniform_bins,
|
|
42
|
+
external_weighted, iter_external_span_items,
|
|
43
|
+
iter_external_hash_dedup, iter_external_top_k_priorities,
|
|
44
|
+
iter_external_uniform_priorities,
|
|
45
|
+
iter_external_weighted_priorities)
|
|
46
|
+
from .curate import CurateResult, curate
|
|
47
|
+
from .dsl import compile_metric, register_compiled, register_metric
|
|
48
|
+
from .experiment import Arm, run_experiment, union_define
|
|
49
|
+
from .features import FeatureEngine
|
|
50
|
+
from .infer import infer_schema
|
|
51
|
+
from .io import exists, local_copy, read_text, write_bytes, write_text
|
|
52
|
+
from .loaders import iter_shards, read_shard_arrays, shard_entries
|
|
53
|
+
from .inspection import compare_datasets, inspect_dataset
|
|
54
|
+
from .importing import import_dataset
|
|
55
|
+
from .html_report import render_html_report, write_html_report
|
|
56
|
+
from .manifest import build_config, dump_segment_manifest, write_config
|
|
57
|
+
from .methods import apply_filters, method_priorities, register_filter
|
|
58
|
+
from .pipeline import Pipeline, input
|
|
59
|
+
from . import builtin_operators # noqa: F401 -- registers every built-in Method/Filter/Scorer
|
|
60
|
+
# as a named Pipeline operator; see builtin_operators.py
|
|
61
|
+
from .plugins import load_plugins, loaded_plugin_modules, plugin_failures, plugin_provenance
|
|
62
|
+
from .plugin_validation import PluginCheckResult, check_plugins
|
|
63
|
+
from .progress import (ProgressCallback, ProgressSpec, cli_progress, notebook_progress,
|
|
64
|
+
progress_callback)
|
|
65
|
+
from .report import dataset_report
|
|
66
|
+
from .stats import stats
|
|
67
|
+
from .review import hours_selection_review, preview_dataset, selection_review
|
|
68
|
+
from .quality import (QualityGateError, check_quality, evaluate_quality,
|
|
69
|
+
validate_expectations)
|
|
70
|
+
from .scorers import SegmentScorer, apply_scorers, get_scorer, register_scorer
|
|
71
|
+
from .output import (
|
|
72
|
+
Container,
|
|
73
|
+
Modality,
|
|
74
|
+
OutputEncoder,
|
|
75
|
+
OutputSpec,
|
|
76
|
+
PassthroughEncoder,
|
|
77
|
+
Track,
|
|
78
|
+
fill_to_hours,
|
|
79
|
+
get_encoder,
|
|
80
|
+
register_encoder,
|
|
81
|
+
register_container,
|
|
82
|
+
output_preset,
|
|
83
|
+
tracks_from_ticks,
|
|
84
|
+
write_dataset,
|
|
85
|
+
write_track,
|
|
86
|
+
)
|
|
87
|
+
from .schema import FieldSpec, MediaSpec, SchemaSpec
|
|
88
|
+
from .media import FrameRef, MediaProvider, PyAVMediaProvider, VideoMetadata
|
|
89
|
+
from .transforms import (BatchTransform, TransformContext, apply_transforms,
|
|
90
|
+
apply_transforms_to_windows, get_transform,
|
|
91
|
+
register_batch_transform, register_transform)
|
|
92
|
+
from .analyzers import (
|
|
93
|
+
Analyzer,
|
|
94
|
+
DatasetAnalyzer,
|
|
95
|
+
SplitLeakageAnalyzer,
|
|
96
|
+
StreamAnalyzer,
|
|
97
|
+
get_analyzer,
|
|
98
|
+
register_analyzer,
|
|
99
|
+
run_dataset_analyzer,
|
|
100
|
+
run_stream_analyzer,
|
|
101
|
+
)
|
|
102
|
+
from .execution import (ExecutionBackend, LocalExecutionBackend, RayExecutionBackend,
|
|
103
|
+
ResourceSpec, get_execution_backend)
|
|
104
|
+
from .planning import (OperatorResources, OperatorTraits, PhysicalOperator, PhysicalPlan,
|
|
105
|
+
PhysicalStage, compile_physical_plan)
|
|
106
|
+
from .scoring import score_sources
|
|
107
|
+
from .segment import Segment, Segmenter
|
|
108
|
+
from .select import assign_splits
|
|
109
|
+
from .strategy import Strategy, load_strategy, segment_priorities, select
|
|
110
|
+
from .registry import (ComponentDescriptor, Registry, default_registry, describe_component,
|
|
111
|
+
list_components, register_method, register_operator, register_preset)
|
|
112
|
+
from .results import (DatasetComparison, DatasetInspection, DatasetPreview, DatasetReport,
|
|
113
|
+
DatasetWriteResult, ExecutionPlan, ExperimentResult,
|
|
114
|
+
HoursSelectionReview, QualityGateResult, SelectionReview,
|
|
115
|
+
StatsExamples, StatsReport, ValidationResult)
|
|
116
|
+
from .versioning import (ARTIFACT_FORMAT_VERSION, JOB_SCHEMA_VERSION, migrate,
|
|
117
|
+
migrate_config, migrate_job)
|
|
118
|
+
|
|
119
|
+
__all__ = [
|
|
120
|
+
"__version__",
|
|
121
|
+
"SchemaSpec", "FieldSpec", "MediaSpec",
|
|
122
|
+
"WindowBuffer", "Tick", "build_window", "empty_like",
|
|
123
|
+
"SegmentBatch", "IDENTITY_COLUMNS", "iter_segment_batches",
|
|
124
|
+
"CheckpointIdentity", "PartitionCommit", "StageCheckpoint", "canonical_hash",
|
|
125
|
+
"FeatureStageResult", "checkpoint_feature_stage",
|
|
126
|
+
"external_quantile_edges", "external_top_k",
|
|
127
|
+
"external_uniform_bins",
|
|
128
|
+
"iter_external_top_k_priorities", "iter_external_span_items",
|
|
129
|
+
"iter_external_uniform_priorities",
|
|
130
|
+
"external_weighted", "iter_external_weighted_priorities",
|
|
131
|
+
"iter_external_hash_dedup",
|
|
132
|
+
"compile_metric", "register_metric", "register_compiled",
|
|
133
|
+
"InputAdapter", "TimeseriesAdapter", "HDF5Adapter", "NpzAdapter", "JsonlAdapter",
|
|
134
|
+
"CuratedDatasetAdapter", "ParquetDatasetAdapter", "HuggingFaceAdapter", "ZarrAdapter",
|
|
135
|
+
"schema_from_output", "register_adapter", "get_adapter", "detect_adapter",
|
|
136
|
+
"FrameRef", "VideoMetadata", "MediaProvider", "PyAVMediaProvider",
|
|
137
|
+
"VideoAdapter", "JoinedMediaAdapter",
|
|
138
|
+
"BatchTransform", "TransformContext", "register_batch_transform", "register_transform",
|
|
139
|
+
"get_transform",
|
|
140
|
+
"apply_transforms", "apply_transforms_to_windows",
|
|
141
|
+
"Analyzer", "DatasetAnalyzer", "StreamAnalyzer", "SplitLeakageAnalyzer",
|
|
142
|
+
"register_analyzer", "get_analyzer", "run_dataset_analyzer", "run_stream_analyzer",
|
|
143
|
+
"ExecutionBackend", "LocalExecutionBackend", "RayExecutionBackend", "ResourceSpec",
|
|
144
|
+
"get_execution_backend",
|
|
145
|
+
"OperatorResources", "OperatorTraits", "PhysicalOperator", "PhysicalStage",
|
|
146
|
+
"PhysicalPlan", "compile_physical_plan",
|
|
147
|
+
"Arm", "run_experiment", "union_define",
|
|
148
|
+
"Segment", "Segmenter", "FeatureEngine",
|
|
149
|
+
"bin_segments", "quantile_bins",
|
|
150
|
+
"Strategy", "load_strategy", "select", "segment_priorities", "assign_splits",
|
|
151
|
+
"apply_filters", "method_priorities", "score_sources", "dataset_report", "stats",
|
|
152
|
+
"register_method", "register_filter", "register_operator",
|
|
153
|
+
"SegmentScorer", "register_scorer", "get_scorer", "apply_scorers",
|
|
154
|
+
"curate", "CurateResult",
|
|
155
|
+
"build_config", "dump_segment_manifest", "write_config",
|
|
156
|
+
"read_text", "write_text", "write_bytes", "exists", "local_copy",
|
|
157
|
+
"OutputSpec", "Modality", "OutputEncoder", "PassthroughEncoder",
|
|
158
|
+
"register_encoder", "get_encoder", "Track", "tracks_from_ticks", "write_track",
|
|
159
|
+
"fill_to_hours", "write_dataset",
|
|
160
|
+
"Container", "register_container", "output_preset", "register_preset",
|
|
161
|
+
"iter_shards", "read_shard_arrays", "shard_entries",
|
|
162
|
+
"inspect_dataset", "DatasetInspection", "compare_datasets", "DatasetComparison",
|
|
163
|
+
"import_dataset",
|
|
164
|
+
"preview_dataset", "DatasetPreview", "selection_review", "SelectionReview",
|
|
165
|
+
"hours_selection_review", "HoursSelectionReview",
|
|
166
|
+
"evaluate_quality", "check_quality", "validate_expectations",
|
|
167
|
+
"QualityGateResult", "QualityGateError",
|
|
168
|
+
"render_html_report", "write_html_report",
|
|
169
|
+
"infer_schema", "Pipeline", "input",
|
|
170
|
+
"load_plugins", "loaded_plugin_modules", "plugin_failures", "plugin_provenance",
|
|
171
|
+
"check_plugins",
|
|
172
|
+
"PluginCheckResult", "ProgressCallback", "ProgressSpec", "progress_callback",
|
|
173
|
+
"notebook_progress", "cli_progress",
|
|
174
|
+
"Registry", "default_registry", "ComponentDescriptor", "list_components",
|
|
175
|
+
"describe_component", "ExecutionPlan", "ARTIFACT_FORMAT_VERSION",
|
|
176
|
+
"DatasetWriteResult", "ExperimentResult", "DatasetReport", "StatsReport",
|
|
177
|
+
"StatsExamples",
|
|
178
|
+
"ValidationResult",
|
|
179
|
+
"JOB_SCHEMA_VERSION", "migrate", "migrate_job", "migrate_config",
|
|
180
|
+
]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Input adapters and validation helpers used by the pipeline facade.
|
|
2
|
+
|
|
3
|
+
Kept separate so ``pipeline.py`` can focus on pipeline plan state and terminal execution. These
|
|
4
|
+
objects remain module-level because in-memory adapters must be pickleable.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from .adapters.timeseries import TimeseriesAdapter
|
|
15
|
+
from .schema import SchemaSpec
|
|
16
|
+
|
|
17
|
+
GLOB_CHARS = set("*?[")
|
|
18
|
+
FILTER_TYPES = {"anomaly", "dedup"}
|
|
19
|
+
OPTION_KEYS = {"workers", "on_error", "seed", "streaming", "resume", "sample_hz", "progress",
|
|
20
|
+
"mode", "executor", "memory_budget", "batch_size", "checkpoint_dir",
|
|
21
|
+
"spill_dir", "approximation", "max_in_flight"}
|
|
22
|
+
TABULAR_KINDS = {"timeseries", "jsonl", "json-rows", "parquet-dataset"}
|
|
23
|
+
SUFFIX_CONTAINER = {".hdf5": "hdf5", ".h5": "hdf5", ".npz": "npz",
|
|
24
|
+
".parquet": "parquet", ".json": "json"}
|
|
25
|
+
|
|
26
|
+
OP_ALIASES = {
|
|
27
|
+
"<=": "<=", "le": "<=", "lte": "<=", "lessorequal": "<=", "leq": "<=",
|
|
28
|
+
">=": ">=", "ge": ">=", "gte": ">=", "greaterorequal": ">=", "geq": ">=",
|
|
29
|
+
"<": "<", "lt": "<", "less": "<", ">": ">", "gt": ">", "greater": ">",
|
|
30
|
+
"==": "==", "eq": "==", "equal": "==", "=": "==",
|
|
31
|
+
"!=": "!=", "ne": "!=", "neq": "!=", "notequal": "!=",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
METHOD_ALIASES = {
|
|
35
|
+
"equal": "uniform-bins", "uniform": "uniform-bins", "uniform-bins": "uniform-bins",
|
|
36
|
+
"top": "top-k", "top-k": "top-k", "topk": "top-k",
|
|
37
|
+
"target": "target-distribution", "target-distribution": "target-distribution",
|
|
38
|
+
"cluster": "cluster-balanced", "cluster-balanced": "cluster-balanced",
|
|
39
|
+
"facility": "facility-location", "facility-location": "facility-location",
|
|
40
|
+
"weighted": "weighted", "k-center": "k-center", "dsir": "dsir",
|
|
41
|
+
"rare": "rare", "typical": "typical", "filter": "filter",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def jsonable(value):
|
|
46
|
+
"""Return a compact, stable, publishable provenance representation."""
|
|
47
|
+
if value is None or isinstance(value, (bool, int, float)):
|
|
48
|
+
return value
|
|
49
|
+
if isinstance(value, str):
|
|
50
|
+
looks_private = (
|
|
51
|
+
"://" in value
|
|
52
|
+
or value.startswith(("/", "\\", "file://"))
|
|
53
|
+
or (len(value) >= 3 and value[1] == ":" and value[2] in "\\/")
|
|
54
|
+
)
|
|
55
|
+
if looks_private:
|
|
56
|
+
from .provenance import publishable_source_id
|
|
57
|
+
return publishable_source_id(value)
|
|
58
|
+
return value
|
|
59
|
+
if isinstance(value, Path):
|
|
60
|
+
from .provenance import publishable_source_id
|
|
61
|
+
return publishable_source_id(value)
|
|
62
|
+
if isinstance(value, np.generic):
|
|
63
|
+
return jsonable(value.item())
|
|
64
|
+
if isinstance(value, np.ndarray):
|
|
65
|
+
return {
|
|
66
|
+
"type": "numpy.ndarray",
|
|
67
|
+
"shape": list(value.shape),
|
|
68
|
+
"dtype": str(value.dtype),
|
|
69
|
+
"content_sha256": hashlib.sha256(value.tobytes(order="C")).hexdigest(),
|
|
70
|
+
}
|
|
71
|
+
if isinstance(value, bytes):
|
|
72
|
+
return {
|
|
73
|
+
"type": "bytes",
|
|
74
|
+
"size": len(value),
|
|
75
|
+
"content_sha256": hashlib.sha256(value).hexdigest(),
|
|
76
|
+
}
|
|
77
|
+
if isinstance(value, (list, tuple)):
|
|
78
|
+
return [jsonable(item) for item in value]
|
|
79
|
+
if isinstance(value, dict):
|
|
80
|
+
return {str(jsonable(key)): jsonable(item) for key, item in value.items()}
|
|
81
|
+
to_dict = getattr(value, "to_dict", None)
|
|
82
|
+
if callable(to_dict):
|
|
83
|
+
return jsonable(to_dict())
|
|
84
|
+
from .provenance import component_identity
|
|
85
|
+
try:
|
|
86
|
+
identity = component_identity(value)
|
|
87
|
+
except Exception:
|
|
88
|
+
identity = {
|
|
89
|
+
"type": f"{type(value).__module__}.{type(value).__qualname__}",
|
|
90
|
+
"identity": "unavailable",
|
|
91
|
+
}
|
|
92
|
+
return {"component": identity}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def describe_src(src) -> Any:
|
|
96
|
+
from .provenance import publishable_source_id
|
|
97
|
+
if isinstance(src, str):
|
|
98
|
+
return publishable_source_id(src)
|
|
99
|
+
if isinstance(src, (list, tuple)) and src and all(isinstance(item, str) for item in src):
|
|
100
|
+
return [publishable_source_id(item) for item in src]
|
|
101
|
+
return f"<in-memory {type(src).__name__}>"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def auto_qualify(token: str, schema: SchemaSpec) -> str:
|
|
105
|
+
if "." in token:
|
|
106
|
+
return token
|
|
107
|
+
if token in schema.fields:
|
|
108
|
+
return f"{schema.entity_axis}.{token}" if schema.entity_axis else token
|
|
109
|
+
if token in schema.globals:
|
|
110
|
+
return token
|
|
111
|
+
if token in schema.event_streams:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"{token!r} is an event stream — name a field: '{token}.<field>' "
|
|
114
|
+
f"(fields: {sorted(schema.event_streams[token]['fields'])})")
|
|
115
|
+
raise ValueError(f"unknown metric source {token!r}; schema fields: "
|
|
116
|
+
f"{sorted(schema.fields)}, globals: {sorted(schema.globals)}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def default_field_map(schema: SchemaSpec, *, tabular: bool) -> dict:
|
|
120
|
+
out: dict = {}
|
|
121
|
+
for name, field in schema.fields.items():
|
|
122
|
+
out[name] = ([f"{name}.{i}" for i in range(len(field.dims))]
|
|
123
|
+
if tabular and field.kind == "vector" else name)
|
|
124
|
+
return out
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def expand_row(row: dict) -> dict:
|
|
128
|
+
out = dict(row)
|
|
129
|
+
for key, value in row.items():
|
|
130
|
+
if isinstance(value, np.ndarray) and value.ndim == 1:
|
|
131
|
+
value = value.tolist()
|
|
132
|
+
if isinstance(value, (list, tuple)):
|
|
133
|
+
for index, item in enumerate(value):
|
|
134
|
+
out[f"{key}.{index}"] = item
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class RowsAdapter(TimeseriesAdapter):
|
|
139
|
+
in_memory = True
|
|
140
|
+
|
|
141
|
+
def __init__(self, schema, *, data: dict, fields: dict, time_column=None,
|
|
142
|
+
entity_column=None, globals=None):
|
|
143
|
+
super().__init__(schema, fields=fields, time_column=time_column,
|
|
144
|
+
entity_column=entity_column, globals=globals)
|
|
145
|
+
self._data = data
|
|
146
|
+
|
|
147
|
+
def ticks(self, source_uri):
|
|
148
|
+
if source_uri not in self._data:
|
|
149
|
+
raise ValueError(f"unknown in-memory source {source_uri!r} "
|
|
150
|
+
f"(have: {sorted(self._data)})")
|
|
151
|
+
return self._rows_to_ticks([expand_row(row) for row in self._data[source_uri]])
|
|
152
|
+
|
|
153
|
+
def source_fingerprint(self, source_uri):
|
|
154
|
+
from .provenance import _state_hash
|
|
155
|
+
return _state_hash(self._data[source_uri])
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TickAdapter:
|
|
159
|
+
in_memory = True
|
|
160
|
+
|
|
161
|
+
def __init__(self, schema, data: dict):
|
|
162
|
+
self.schema = schema
|
|
163
|
+
self._data = data
|
|
164
|
+
|
|
165
|
+
def ticks(self, source_uri):
|
|
166
|
+
if source_uri not in self._data:
|
|
167
|
+
raise ValueError(f"unknown in-memory source {source_uri!r} "
|
|
168
|
+
f"(have: {sorted(self._data)})")
|
|
169
|
+
return self._data[source_uri]
|
|
170
|
+
|
|
171
|
+
def source_fingerprint(self, source_uri):
|
|
172
|
+
from .provenance import _state_hash
|
|
173
|
+
return _state_hash(self._data[source_uri])
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class JsonRowsAdapter(TimeseriesAdapter):
|
|
177
|
+
def ticks(self, source_uri):
|
|
178
|
+
from .infer import load_rows
|
|
179
|
+
return self._rows_to_ticks([expand_row(row) for row in load_rows(source_uri)])
|
|
180
|
+
|
|
181
|
+
def iter_ticks(self, source_uri):
|
|
182
|
+
yield from self.ticks(source_uri)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Shared collision-safe idempotence for built-in component registration."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def needs_registration(registry, kind: str, name: str, component) -> bool:
|
|
6
|
+
"""Return whether a component is absent, accepting only exact repeat registration."""
|
|
7
|
+
if name not in registry.names(kind):
|
|
8
|
+
return True
|
|
9
|
+
if registry.get(kind, name) is component:
|
|
10
|
+
return False
|
|
11
|
+
descriptor = registry.descriptor(kind, name)
|
|
12
|
+
owner = descriptor.module or descriptor.package or "an unknown implementation"
|
|
13
|
+
raise ValueError(
|
|
14
|
+
f"cannot register {kind} {name!r}: the name is already owned by {owner!r}"
|
|
15
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Input adapters: raw source -> canonical Tick stream."""
|
|
2
|
+
from .base import InputAdapter, detect_adapter, get_adapter, register_adapter
|
|
3
|
+
from .curated import CuratedDatasetAdapter, schema_from_output
|
|
4
|
+
from .hdf5 import HDF5Adapter
|
|
5
|
+
from .huggingface import HuggingFaceAdapter
|
|
6
|
+
from .jsonl import JsonlAdapter
|
|
7
|
+
from .npz import NpzAdapter
|
|
8
|
+
from .parquet_dataset import ParquetDatasetAdapter
|
|
9
|
+
from .timeseries import TimeseriesAdapter
|
|
10
|
+
from .zarr_store import ZarrAdapter
|
|
11
|
+
from ..media import JoinedMediaAdapter, VideoAdapter
|
|
12
|
+
|
|
13
|
+
__all__ = ["InputAdapter", "register_adapter", "get_adapter", "detect_adapter",
|
|
14
|
+
"TimeseriesAdapter",
|
|
15
|
+
"HDF5Adapter", "NpzAdapter", "JsonlAdapter", "CuratedDatasetAdapter",
|
|
16
|
+
"ParquetDatasetAdapter", "HuggingFaceAdapter", "ZarrAdapter",
|
|
17
|
+
"schema_from_output"]
|
|
18
|
+
__all__ += ["VideoAdapter", "JoinedMediaAdapter"]
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""`InputAdapter` — the input seam. The one place domain knowledge about a raw format
|
|
2
|
+
enters the SDK.
|
|
3
|
+
|
|
4
|
+
An adapter's contract is deliberately tiny: turn a raw source into an ordered stream of
|
|
5
|
+
canonical `Tick`s (parse + group-by-timestep). Everything after that — building the
|
|
6
|
+
`(T,E,D)` buffer, segmentation, metrics, selection — is generic SDK code that never knows
|
|
7
|
+
which dataset it's looking at. Streaming-capable adapters expose `iter_ticks`; the compatible
|
|
8
|
+
`ticks` path materializes a list for callers that need random access.
|
|
9
|
+
|
|
10
|
+
Adapters are registered by name for declarative job construction. The parallel feature pass
|
|
11
|
+
pickles the configured adapter into spawned workers.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Callable, Iterable, Protocol, runtime_checkable
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from ..buffer import Tick
|
|
20
|
+
from ..schema import SchemaSpec
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class InputAdapter(Protocol):
|
|
25
|
+
schema: SchemaSpec
|
|
26
|
+
def ticks(self, source_uri: str) -> Iterable[Tick]: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_adapter_availability(adapter, schema: SchemaSpec, *, define: dict | None = None,
|
|
30
|
+
tags_spec: dict | None = None, output_spec=None) -> None:
|
|
31
|
+
"""Reject plans that depend on schema values a configured adapter cannot emit.
|
|
32
|
+
|
|
33
|
+
Third-party adapters that do not declare ``available_fields``/``available_globals`` retain
|
|
34
|
+
the protocol's historical promise that they can emit the full schema. Built-in configured
|
|
35
|
+
adapters expose these sets, allowing a typo or partial mapping to fail before data is read
|
|
36
|
+
instead of becoming an all-missing buffer that reduces to a plausible zero.
|
|
37
|
+
"""
|
|
38
|
+
available_fields = getattr(adapter, "available_fields", None)
|
|
39
|
+
available_globals = getattr(adapter, "available_globals", None)
|
|
40
|
+
if available_fields is None and available_globals is None:
|
|
41
|
+
return
|
|
42
|
+
available_fields = set(schema.fields if available_fields is None else available_fields)
|
|
43
|
+
available_globals = set(schema.globals if available_globals is None else available_globals)
|
|
44
|
+
required_fields: set[str] = set()
|
|
45
|
+
required_globals: set[str] = set()
|
|
46
|
+
for spec in (define or {}).values():
|
|
47
|
+
if not isinstance(spec, dict):
|
|
48
|
+
continue
|
|
49
|
+
source = spec.get("source")
|
|
50
|
+
if isinstance(source, str):
|
|
51
|
+
parts = source.split(".")
|
|
52
|
+
if schema.entity_axis is not None and len(parts) >= 2 and parts[0] == schema.entity_axis:
|
|
53
|
+
required_fields.add(parts[1])
|
|
54
|
+
elif source in schema.globals:
|
|
55
|
+
required_globals.add(source)
|
|
56
|
+
filter_expr = spec.get("filter")
|
|
57
|
+
if isinstance(filter_expr, str):
|
|
58
|
+
from ..dsl.grammar import _parse_filter
|
|
59
|
+
field = _parse_filter(filter_expr)[1]
|
|
60
|
+
if field in schema.fields:
|
|
61
|
+
required_fields.add(field)
|
|
62
|
+
for source in ((tags_spec or {}).get("from") or ()):
|
|
63
|
+
if source in schema.fields:
|
|
64
|
+
required_fields.add(source)
|
|
65
|
+
elif source in schema.globals:
|
|
66
|
+
required_globals.add(source)
|
|
67
|
+
if output_spec is not None:
|
|
68
|
+
for modality in output_spec.modalities:
|
|
69
|
+
if modality.src_kind == "field":
|
|
70
|
+
required_fields.add(modality.src_arg)
|
|
71
|
+
missing_fields = sorted(required_fields - available_fields)
|
|
72
|
+
missing_globals = sorted(required_globals - available_globals)
|
|
73
|
+
if missing_fields or missing_globals:
|
|
74
|
+
details = []
|
|
75
|
+
if missing_fields:
|
|
76
|
+
details.append(f"fields {missing_fields}")
|
|
77
|
+
if missing_globals:
|
|
78
|
+
details.append(f"globals {missing_globals}")
|
|
79
|
+
raise ValueError(f"{type(adapter).__name__} does not map required " + " and ".join(details))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def iter_arrays_to_ticks(schema: SchemaSpec, arrs: dict, gl: dict = None):
|
|
83
|
+
"""Lazily build single-entity ticks from indexable per-field arrays."""
|
|
84
|
+
gl = gl or {}
|
|
85
|
+
overlap = sorted(set(arrs) & set(gl))
|
|
86
|
+
if overlap:
|
|
87
|
+
raise ValueError(f"arrays_to_ticks: names cannot be both fields and globals: {overlap}")
|
|
88
|
+
arrays = {**arrs, **gl}
|
|
89
|
+
|
|
90
|
+
def step_count(array) -> int:
|
|
91
|
+
# Zarr 3 arrays deliberately do not implement ``len``. Array stores do expose
|
|
92
|
+
# ``shape`` as metadata, while ordinary Python sequences still use ``len``.
|
|
93
|
+
shape = getattr(array, "shape", None)
|
|
94
|
+
return int(shape[0]) if shape else (0 if shape == () else len(array))
|
|
95
|
+
|
|
96
|
+
n = step_count(next(iter(arrays.values()))) if arrays else 0
|
|
97
|
+
mismatched = {k: step_count(a) for k, a in arrays.items() if step_count(a) != n}
|
|
98
|
+
if mismatched: # silent truncation to the first field's length is worse
|
|
99
|
+
raise ValueError(f"arrays_to_ticks: length mismatch — expected {n} steps, got "
|
|
100
|
+
f"{mismatched}")
|
|
101
|
+
import math
|
|
102
|
+
for fn, arr in arrs.items():
|
|
103
|
+
if fn not in schema.fields:
|
|
104
|
+
raise ValueError(f"arrays_to_ticks: undeclared schema field {fn!r}")
|
|
105
|
+
shape = tuple(getattr(arr, "shape", np.asarray(arr).shape))
|
|
106
|
+
width = math.prod(shape[1:]) if shape else 0
|
|
107
|
+
expected = schema.fields[fn].width
|
|
108
|
+
if width != expected:
|
|
109
|
+
raise ValueError(f"arrays_to_ticks: field {fn!r} has per-step width {width}, "
|
|
110
|
+
f"expected {expected} for {schema.fields[fn].kind}")
|
|
111
|
+
for gn, arr in gl.items():
|
|
112
|
+
if gn not in schema.globals:
|
|
113
|
+
raise ValueError(f"arrays_to_ticks: undeclared schema global {gn!r}")
|
|
114
|
+
shape = tuple(getattr(arr, "shape", np.asarray(arr).shape))
|
|
115
|
+
width = math.prod(shape[1:]) if shape else 0
|
|
116
|
+
if width != 1:
|
|
117
|
+
raise ValueError(f"arrays_to_ticks: global {gn!r} has per-step width {width}, "
|
|
118
|
+
"expected 1")
|
|
119
|
+
for t in range(n):
|
|
120
|
+
ent: dict[str, object] = {}
|
|
121
|
+
for fn, arr in arrs.items():
|
|
122
|
+
fs = schema.fields[fn]
|
|
123
|
+
x = arr[t]
|
|
124
|
+
if fs.kind == "vector":
|
|
125
|
+
ent[fn] = tuple(float(v) for v in np.ravel(x))
|
|
126
|
+
elif fs.kind == "categorical":
|
|
127
|
+
ent[fn] = x.decode() if isinstance(x, bytes) else str(x)
|
|
128
|
+
else:
|
|
129
|
+
ent[fn] = float(np.ravel(x)[0]) if np.ndim(x) else float(x)
|
|
130
|
+
g = {gn: float(np.ravel(arr[t])[0]) for gn, arr in gl.items()}
|
|
131
|
+
yield Tick(time=t, entities={0: ent}, globals=g)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def arrays_to_ticks(schema: SchemaSpec, arrs: dict, gl: dict = None) -> list:
|
|
135
|
+
"""Materialized counterpart to `iter_arrays_to_ticks` for existing callers."""
|
|
136
|
+
return list(iter_arrays_to_ticks(schema, arrs, gl))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def check_mapped_arrays(schema: SchemaSpec, fields: dict, globals_: dict, source_uri: str,
|
|
140
|
+
shape_of: Callable) -> None:
|
|
141
|
+
"""Metadata-level mirror of `arrays_to_ticks`'s run-time checks, shared by the array-store
|
|
142
|
+
adapters' `check_source` hooks. `shape_of(key)` returns the stored array's shape, or None
|
|
143
|
+
if the key doesn't exist — shapes are store metadata, so no array data is read. Checks:
|
|
144
|
+
every mapped key exists, all first dims agree (step count), and each field's per-step
|
|
145
|
+
width matches the schema (a wide array mapped to a scalar would silently truncate to its
|
|
146
|
+
first component at run time — loud beats masked)."""
|
|
147
|
+
import math
|
|
148
|
+
overlap = sorted(set(fields) & set(globals_))
|
|
149
|
+
if overlap:
|
|
150
|
+
raise ValueError(f"source {source_uri!r}: names cannot be both fields and globals: "
|
|
151
|
+
f"{overlap}")
|
|
152
|
+
keys = {**fields, **globals_} # mapped name -> store key
|
|
153
|
+
shapes, missing = {}, []
|
|
154
|
+
for key in keys.values():
|
|
155
|
+
shp = shape_of(key)
|
|
156
|
+
if shp is None:
|
|
157
|
+
missing.append(key)
|
|
158
|
+
else:
|
|
159
|
+
shapes[key] = tuple(shp)
|
|
160
|
+
if missing:
|
|
161
|
+
raise ValueError(f"source {source_uri!r} is missing mapped key(s) {sorted(missing)}")
|
|
162
|
+
lens = {k: (s[0] if s else 0) for k, s in shapes.items()}
|
|
163
|
+
if len(set(lens.values())) > 1:
|
|
164
|
+
raise ValueError(f"source {source_uri!r}: mapped arrays disagree on step count: {lens}")
|
|
165
|
+
for f, key in fields.items():
|
|
166
|
+
fs = schema.fields[f]
|
|
167
|
+
width = math.prod(shapes[key][1:])
|
|
168
|
+
if width != fs.width:
|
|
169
|
+
raise ValueError(f"source {source_uri!r}: array {key!r} has per-step width {width}, "
|
|
170
|
+
f"but field {f!r} ({fs.kind}) expects {fs.width}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_ADAPTERS: dict[str, Callable[..., InputAdapter]] = {}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def register_adapter(name: str, factory: Callable[..., InputAdapter], **metadata) -> None:
|
|
177
|
+
"""Register an input-adapter factory under a unique name.
|
|
178
|
+
|
|
179
|
+
``metadata`` becomes its component descriptor. Duplicate names raise ``ValueError``.
|
|
180
|
+
"""
|
|
181
|
+
from ..registry import default_registry
|
|
182
|
+
default_registry.register("adapter", name, factory, **metadata)
|
|
183
|
+
_ADAPTERS[name] = factory
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_adapter(name: str, *, registry=None, **kwargs) -> InputAdapter:
|
|
187
|
+
"""Construct a named adapter, loading plugins on demand.
|
|
188
|
+
|
|
189
|
+
``kwargs`` are passed to the factory. An explicit ``registry`` bypasses the legacy global
|
|
190
|
+
adapter table; an unknown name raises ``ValueError``.
|
|
191
|
+
"""
|
|
192
|
+
from ..registry import default_registry
|
|
193
|
+
active_registry = registry or default_registry
|
|
194
|
+
if name not in active_registry.names("adapter") and active_registry is default_registry:
|
|
195
|
+
from ..plugins import load_plugins
|
|
196
|
+
load_plugins()
|
|
197
|
+
return active_registry.get("adapter", name)(**kwargs)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def detect_adapter(source_uri: str, *, registry=None) -> str:
|
|
201
|
+
"""Return the uniquely highest-confidence registered adapter for a source.
|
|
202
|
+
|
|
203
|
+
Adapter factories may expose ``detect_source(uri) -> bool | number``. A numeric result is
|
|
204
|
+
a confidence score; ``True`` is score 1 and falsey values mean no match. Detection is
|
|
205
|
+
intentionally separate from construction because many adapters need inferred schema and
|
|
206
|
+
mapping parameters before they can be instantiated.
|
|
207
|
+
"""
|
|
208
|
+
from ..registry import default_registry
|
|
209
|
+
|
|
210
|
+
active_registry = registry or default_registry
|
|
211
|
+
if active_registry is default_registry:
|
|
212
|
+
from ..plugins import load_plugins
|
|
213
|
+
|
|
214
|
+
load_plugins()
|
|
215
|
+
matches: list[tuple[float, str]] = []
|
|
216
|
+
for name in active_registry.names("adapter"):
|
|
217
|
+
factory = active_registry.get("adapter", name)
|
|
218
|
+
detector = getattr(factory, "detect_source", None)
|
|
219
|
+
if not callable(detector):
|
|
220
|
+
continue
|
|
221
|
+
confidence = detector(str(source_uri))
|
|
222
|
+
if isinstance(confidence, bool):
|
|
223
|
+
score = 1.0 if confidence else 0.0
|
|
224
|
+
elif isinstance(confidence, (int, float)) and not isinstance(confidence, bool):
|
|
225
|
+
score = float(confidence)
|
|
226
|
+
else:
|
|
227
|
+
raise ValueError(
|
|
228
|
+
f"adapter {name!r} detect_source() must return a boolean or number"
|
|
229
|
+
)
|
|
230
|
+
if not np.isfinite(score) or score < 0:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"adapter {name!r} detect_source() returned invalid confidence {score!r}"
|
|
233
|
+
)
|
|
234
|
+
if score > 0:
|
|
235
|
+
matches.append((score, name))
|
|
236
|
+
if not matches:
|
|
237
|
+
raise ValueError(
|
|
238
|
+
f"no registered input adapter recognized {source_uri!r}; "
|
|
239
|
+
"pass adapter= explicitly or install/register a matching adapter"
|
|
240
|
+
)
|
|
241
|
+
best = max(score for score, _name in matches)
|
|
242
|
+
winners = sorted(name for score, name in matches if score == best)
|
|
243
|
+
if len(winners) > 1:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"input adapter detection for {source_uri!r} is ambiguous at confidence "
|
|
246
|
+
f"{best:g}: {winners}; pass adapter= explicitly"
|
|
247
|
+
)
|
|
248
|
+
return winners[0]
|