dataeval-flow 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.
- dataeval_flow/__init__.py +93 -0
- dataeval_flow/__main__.py +149 -0
- dataeval_flow/_app/__init__.py +5 -0
- dataeval_flow/_app/_model/__init__.py +5 -0
- dataeval_flow/_app/_model/_coerce.py +126 -0
- dataeval_flow/_app/_model/_discover.py +171 -0
- dataeval_flow/_app/_model/_execution.py +108 -0
- dataeval_flow/_app/_model/_introspect.py +280 -0
- dataeval_flow/_app/_model/_item.py +213 -0
- dataeval_flow/_app/_model/_registry.py +255 -0
- dataeval_flow/_app/_model/_state.py +322 -0
- dataeval_flow/_app/_model/_undo.py +61 -0
- dataeval_flow/_app/_panes/__init__.py +35 -0
- dataeval_flow/_app/_panes/_config_pane.py +173 -0
- dataeval_flow/_app/_panes/_result_pane.py +125 -0
- dataeval_flow/_app/_panes/_task_pane.py +91 -0
- dataeval_flow/_app/_panes/_widgets.py +111 -0
- dataeval_flow/_app/_screens/__init__.py +25 -0
- dataeval_flow/_app/_screens/_base.py +242 -0
- dataeval_flow/_app/_screens/_detail.py +333 -0
- dataeval_flow/_app/_screens/_model.py +102 -0
- dataeval_flow/_app/_screens/_params.py +80 -0
- dataeval_flow/_app/_screens/_pathpicker.py +68 -0
- dataeval_flow/_app/_screens/_section.py +621 -0
- dataeval_flow/_app/_screens/_settings.py +183 -0
- dataeval_flow/_app/_viewmodel/__init__.py +15 -0
- dataeval_flow/_app/_viewmodel/_builder_vm.py +272 -0
- dataeval_flow/_app/_viewmodel/_model_vm.py +70 -0
- dataeval_flow/_app/_viewmodel/_rendering.py +189 -0
- dataeval_flow/_app/_viewmodel/_result_vm.py +210 -0
- dataeval_flow/_app/_viewmodel/_section_vm.py +224 -0
- dataeval_flow/_app/app.py +742 -0
- dataeval_flow/_app/cli.py +592 -0
- dataeval_flow/_logging.py +102 -0
- dataeval_flow/cache.py +1355 -0
- dataeval_flow/config/__init__.py +80 -0
- dataeval_flow/config/_loader.py +79 -0
- dataeval_flow/config/_merge.py +92 -0
- dataeval_flow/config/_models.py +115 -0
- dataeval_flow/config/_paths.py +85 -0
- dataeval_flow/config/schemas/__init__.py +112 -0
- dataeval_flow/config/schemas/_dataset.py +111 -0
- dataeval_flow/config/schemas/_extractor.py +119 -0
- dataeval_flow/config/schemas/_metadata.py +28 -0
- dataeval_flow/config/schemas/_preprocessor.py +18 -0
- dataeval_flow/config/schemas/_selection.py +100 -0
- dataeval_flow/config/schemas/_task.py +89 -0
- dataeval_flow/config/schemas/_workflow.py +135 -0
- dataeval_flow/dataset.py +635 -0
- dataeval_flow/embeddings.py +135 -0
- dataeval_flow/metadata.py +48 -0
- dataeval_flow/preprocessing.py +141 -0
- dataeval_flow/py.typed +0 -0
- dataeval_flow/runner.py +118 -0
- dataeval_flow/selection.py +50 -0
- dataeval_flow/workflow/__init__.py +328 -0
- dataeval_flow/workflow/_text_report.py +511 -0
- dataeval_flow/workflow/base.py +69 -0
- dataeval_flow/workflow/orchestrator.py +454 -0
- dataeval_flow/workflows/__init__.py +1 -0
- dataeval_flow/workflows/analysis/__init__.py +38 -0
- dataeval_flow/workflows/analysis/outputs.py +202 -0
- dataeval_flow/workflows/analysis/params.py +114 -0
- dataeval_flow/workflows/analysis/workflow.py +1313 -0
- dataeval_flow/workflows/cleaning/__init__.py +23 -0
- dataeval_flow/workflows/cleaning/outputs.py +200 -0
- dataeval_flow/workflows/cleaning/params.py +160 -0
- dataeval_flow/workflows/cleaning/report.py +304 -0
- dataeval_flow/workflows/cleaning/workflow.py +794 -0
- dataeval_flow/workflows/drift/__init__.py +1 -0
- dataeval_flow/workflows/drift/outputs.py +144 -0
- dataeval_flow/workflows/drift/params.py +332 -0
- dataeval_flow/workflows/drift/report.py +201 -0
- dataeval_flow/workflows/drift/workflow.py +647 -0
- dataeval_flow/workflows/ood/__init__.py +1 -0
- dataeval_flow/workflows/ood/outputs.py +134 -0
- dataeval_flow/workflows/ood/params.py +161 -0
- dataeval_flow/workflows/ood/report.py +311 -0
- dataeval_flow/workflows/ood/workflow.py +728 -0
- dataeval_flow/workflows/prioritization/__init__.py +1 -0
- dataeval_flow/workflows/prioritization/outputs.py +122 -0
- dataeval_flow/workflows/prioritization/params.py +124 -0
- dataeval_flow/workflows/prioritization/report.py +117 -0
- dataeval_flow/workflows/prioritization/workflow.py +587 -0
- dataeval_flow/workflows/splitting/__init__.py +25 -0
- dataeval_flow/workflows/splitting/outputs.py +101 -0
- dataeval_flow/workflows/splitting/params.py +61 -0
- dataeval_flow/workflows/splitting/report.py +485 -0
- dataeval_flow/workflows/splitting/workflow.py +371 -0
- dataeval_flow-0.1.0.dist-info/METADATA +305 -0
- dataeval_flow-0.1.0.dist-info/RECORD +94 -0
- dataeval_flow-0.1.0.dist-info/WHEEL +4 -0
- dataeval_flow-0.1.0.dist-info/entry_points.txt +2 -0
- dataeval_flow-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Config layer — single public API for all configuration types.
|
|
2
|
+
|
|
3
|
+
Import everything from here::
|
|
4
|
+
|
|
5
|
+
from dataeval_flow.config import PipelineConfig, OnnxExtractorConfig, ...
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
# Dataset configs
|
|
10
|
+
"CocoDatasetConfig",
|
|
11
|
+
"DatasetProtocolConfig",
|
|
12
|
+
"HuggingFaceDatasetConfig",
|
|
13
|
+
"ImageFolderDatasetConfig",
|
|
14
|
+
"YoloDatasetConfig",
|
|
15
|
+
# Extractor configs
|
|
16
|
+
"BoVWExtractorConfig",
|
|
17
|
+
"FlattenExtractorConfig",
|
|
18
|
+
"OnnxExtractorConfig",
|
|
19
|
+
"TorchExtractorConfig",
|
|
20
|
+
"UncertaintyExtractorConfig",
|
|
21
|
+
# Workflow configs
|
|
22
|
+
"DataAnalysisWorkflowConfig",
|
|
23
|
+
"DataCleaningWorkflowConfig",
|
|
24
|
+
"DataPrioritizationWorkflowConfig",
|
|
25
|
+
"DriftMonitoringWorkflowConfig",
|
|
26
|
+
"OODDetectionWorkflowConfig",
|
|
27
|
+
# Task configs
|
|
28
|
+
"DataAnalysisTaskConfig",
|
|
29
|
+
"DataCleaningTaskConfig",
|
|
30
|
+
"DataPrioritizationTaskConfig",
|
|
31
|
+
"DriftMonitoringTaskConfig",
|
|
32
|
+
"OODDetectionTaskConfig",
|
|
33
|
+
"TaskConfig",
|
|
34
|
+
# Composition / pipeline
|
|
35
|
+
"PipelineConfig",
|
|
36
|
+
"SourceConfig",
|
|
37
|
+
# Other schemas
|
|
38
|
+
"PreprocessorConfig",
|
|
39
|
+
"ResultMetadata",
|
|
40
|
+
"SelectionConfig",
|
|
41
|
+
"SelectionStep",
|
|
42
|
+
# Loader functions
|
|
43
|
+
"export_params_schema",
|
|
44
|
+
"load_config",
|
|
45
|
+
"load_config_folder",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
from dataeval_flow.config._loader import (
|
|
49
|
+
export_params_schema,
|
|
50
|
+
load_config,
|
|
51
|
+
load_config_folder,
|
|
52
|
+
)
|
|
53
|
+
from dataeval_flow.config._models import PipelineConfig, SourceConfig
|
|
54
|
+
from dataeval_flow.config.schemas import (
|
|
55
|
+
BoVWExtractorConfig,
|
|
56
|
+
CocoDatasetConfig,
|
|
57
|
+
DataAnalysisTaskConfig,
|
|
58
|
+
DataAnalysisWorkflowConfig,
|
|
59
|
+
DataCleaningTaskConfig,
|
|
60
|
+
DataCleaningWorkflowConfig,
|
|
61
|
+
DataPrioritizationTaskConfig,
|
|
62
|
+
DataPrioritizationWorkflowConfig,
|
|
63
|
+
DatasetProtocolConfig,
|
|
64
|
+
DriftMonitoringTaskConfig,
|
|
65
|
+
DriftMonitoringWorkflowConfig,
|
|
66
|
+
FlattenExtractorConfig,
|
|
67
|
+
HuggingFaceDatasetConfig,
|
|
68
|
+
ImageFolderDatasetConfig,
|
|
69
|
+
OnnxExtractorConfig,
|
|
70
|
+
OODDetectionTaskConfig,
|
|
71
|
+
OODDetectionWorkflowConfig,
|
|
72
|
+
PreprocessorConfig,
|
|
73
|
+
ResultMetadata,
|
|
74
|
+
SelectionConfig,
|
|
75
|
+
SelectionStep,
|
|
76
|
+
TaskConfig,
|
|
77
|
+
TorchExtractorConfig,
|
|
78
|
+
UncertaintyExtractorConfig,
|
|
79
|
+
YoloDatasetConfig,
|
|
80
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Config loading - YAML/JSON single-file, multi-file merge, schema export."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from dataeval_flow.config._models import PipelineConfig
|
|
11
|
+
from dataeval_flow.config._paths import relativize_to_data_dir, validate_config_path
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"export_params_schema",
|
|
15
|
+
"get_data_dir",
|
|
16
|
+
"load_config",
|
|
17
|
+
"load_config_folder",
|
|
18
|
+
"relativize_to_data_dir",
|
|
19
|
+
"resolve_path",
|
|
20
|
+
"validate_config_path",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
DEFAULT_DATA_DIR = Path(".")
|
|
26
|
+
_DATAEVAL_DATA_ENV = "DATAEVAL_DATA"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_data_dir(data_dir: Path | None = None) -> Path:
|
|
30
|
+
"""Resolve the data root directory.
|
|
31
|
+
|
|
32
|
+
Priority: explicit argument > ``DATAEVAL_DATA`` env var > current directory.
|
|
33
|
+
"""
|
|
34
|
+
if data_dir is not None:
|
|
35
|
+
return data_dir
|
|
36
|
+
return Path(os.environ.get(_DATAEVAL_DATA_ENV, str(DEFAULT_DATA_DIR)))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def resolve_path(relative: str | Path, data_dir: Path | None = None) -> Path:
|
|
40
|
+
"""Resolve a user-provided path against *data_dir*.
|
|
41
|
+
|
|
42
|
+
Absolute paths are returned as-is. Relative paths are joined to
|
|
43
|
+
*data_dir* (which itself defaults via :func:`get_data_dir`).
|
|
44
|
+
"""
|
|
45
|
+
p = Path(relative)
|
|
46
|
+
if p.is_absolute():
|
|
47
|
+
return p
|
|
48
|
+
root = data_dir if data_dir is not None else get_data_dir()
|
|
49
|
+
return root / p
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_config(config_path: Path) -> PipelineConfig:
|
|
53
|
+
"""Load pipeline configuration from a single YAML or JSON file."""
|
|
54
|
+
logger.debug("Loading config from %s", config_path)
|
|
55
|
+
|
|
56
|
+
if not config_path.exists():
|
|
57
|
+
msg = f"Config file not found: {config_path}"
|
|
58
|
+
raise FileNotFoundError(msg)
|
|
59
|
+
|
|
60
|
+
with open(config_path, encoding="utf-8") as f:
|
|
61
|
+
data = json.load(f) if config_path.suffix.lower() == ".json" else yaml.safe_load(f) or {}
|
|
62
|
+
|
|
63
|
+
return PipelineConfig.model_validate(data)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_config_folder(config_path: Path) -> PipelineConfig:
|
|
67
|
+
"""Load and merge all YAML/JSON files from config folder."""
|
|
68
|
+
from dataeval_flow.config._merge import merge_config_folder
|
|
69
|
+
|
|
70
|
+
logger.debug("Loading config folder %s", config_path)
|
|
71
|
+
merged = merge_config_folder(config_path)
|
|
72
|
+
return PipelineConfig.model_validate(merged)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def export_params_schema(output_path: Path) -> None:
|
|
76
|
+
"""Export JSON Schema for params.yaml (PipelineConfig) for IDE validation."""
|
|
77
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
schema = PipelineConfig.model_json_schema()
|
|
79
|
+
output_path.write_text(json.dumps(schema, indent=2), encoding="utf-8")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Multi-file YAML/JSON configuration loader with schema validation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_YAML_EXTS = frozenset({".yaml", ".yml"})
|
|
13
|
+
_JSON_EXTS = frozenset({".json"})
|
|
14
|
+
_CONFIG_EXTS = _YAML_EXTS | _JSON_EXTS
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_file(path: Path) -> dict[str, Any] | list[str]:
|
|
18
|
+
"""Load a single YAML or JSON file and return its contents as a dict."""
|
|
19
|
+
with open(path, encoding="utf-8") as f:
|
|
20
|
+
return json.load(f) if path.suffix.lower() in _JSON_EXTS else yaml.safe_load(f) or []
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_valid_config(data: dict[str, Any]) -> bool:
|
|
24
|
+
"""Return True if *data* looks like a pipeline config fragment.
|
|
25
|
+
|
|
26
|
+
A file is accepted when it contains at least one key that is a known
|
|
27
|
+
``PipelineConfig`` field **and** no keys that are completely unknown.
|
|
28
|
+
This allows partial configs (e.g. only ``datasets:``) while rejecting
|
|
29
|
+
unrelated files like JSON schemas.
|
|
30
|
+
"""
|
|
31
|
+
from dataeval_flow.config._models import PipelineConfig
|
|
32
|
+
|
|
33
|
+
known_keys = set(PipelineConfig.model_fields)
|
|
34
|
+
file_keys = set(data.keys())
|
|
35
|
+
return bool(file_keys) and file_keys <= known_keys
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def merge_config_folder(config_path: Path) -> dict[str, Any]:
|
|
39
|
+
"""Scan folder, merge all valid config files alphabetically.
|
|
40
|
+
|
|
41
|
+
Each candidate file is validated against the ``PipelineConfig`` schema
|
|
42
|
+
individually. Files that fail validation (e.g. JSON schemas, unrelated
|
|
43
|
+
YAML) are silently skipped. If no valid files are found, a
|
|
44
|
+
``FileNotFoundError`` is raised.
|
|
45
|
+
|
|
46
|
+
Files are loaded in sorted order (00-base.yaml before 01-datasets.yaml).
|
|
47
|
+
Later files override earlier ones for duplicate keys.
|
|
48
|
+
|
|
49
|
+
Returns raw dict - use load_config_folder() for validated PipelineConfig.
|
|
50
|
+
"""
|
|
51
|
+
config: dict[str, Any] = {}
|
|
52
|
+
|
|
53
|
+
if not config_path.is_dir():
|
|
54
|
+
raise ValueError(f"Config path is not a directory: {config_path}")
|
|
55
|
+
|
|
56
|
+
candidates = sorted(f for f in config_path.iterdir() if f.is_file() and f.suffix.lower() in _CONFIG_EXTS)
|
|
57
|
+
logger.debug("Found %d candidate file(s): %s", len(candidates), [f.name for f in candidates])
|
|
58
|
+
|
|
59
|
+
accepted: list[Path] = []
|
|
60
|
+
for config_file in candidates:
|
|
61
|
+
try:
|
|
62
|
+
file_config = _load_file(config_file)
|
|
63
|
+
except (json.JSONDecodeError, yaml.YAMLError) as exc:
|
|
64
|
+
logger.debug("Skipping %s (parse error: %s)", config_file.name, exc)
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
if not isinstance(file_config, dict) or not _is_valid_config(file_config):
|
|
68
|
+
logger.debug("Skipping %s (not a valid pipeline config)", config_file.name)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
_deep_merge(config, file_config)
|
|
72
|
+
accepted.append(config_file)
|
|
73
|
+
|
|
74
|
+
if not accepted:
|
|
75
|
+
raise FileNotFoundError(f"No valid pipeline config files found in {config_path}")
|
|
76
|
+
|
|
77
|
+
logger.debug("Accepted %d config file(s): %s", len(accepted), [f.name for f in accepted])
|
|
78
|
+
return config
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _deep_merge(base: dict, overlay: dict) -> None:
|
|
82
|
+
"""Recursively merge overlay into base.
|
|
83
|
+
|
|
84
|
+
Rules: dicts merge recursively, lists extend, scalars replace.
|
|
85
|
+
"""
|
|
86
|
+
for key, value in overlay.items():
|
|
87
|
+
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
|
88
|
+
_deep_merge(base[key], value)
|
|
89
|
+
elif key in base and isinstance(base[key], list) and isinstance(value, list):
|
|
90
|
+
base[key].extend(value)
|
|
91
|
+
else:
|
|
92
|
+
base[key] = value
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Pipeline and workflow composition models — SourceConfig, PipelineConfig."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"PipelineConfig",
|
|
5
|
+
"SourceConfig",
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field, model_validator
|
|
12
|
+
|
|
13
|
+
from dataeval_flow.config.schemas import (
|
|
14
|
+
DatasetConfig,
|
|
15
|
+
DatasetProtocolConfig,
|
|
16
|
+
ExtractorConfig,
|
|
17
|
+
PreprocessorConfig,
|
|
18
|
+
SelectionConfig,
|
|
19
|
+
TaskConfig,
|
|
20
|
+
WorkflowConfig,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Source — dataset + optional selection
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SourceConfig(BaseModel):
|
|
29
|
+
"""Named source definition — bundles a dataset with an optional selection.
|
|
30
|
+
|
|
31
|
+
YAML example::
|
|
32
|
+
|
|
33
|
+
sources:
|
|
34
|
+
- name: cifar_train_subset
|
|
35
|
+
dataset: cifar10_train
|
|
36
|
+
selection: first_5k
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
name: str = Field(description="Identifier for the source")
|
|
40
|
+
dataset: str = Field(description="Reference to a dataset name")
|
|
41
|
+
selection: str | None = Field(default=None, description="Reference to a selection name (optional)")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Logging
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LoggingConfig(BaseModel):
|
|
50
|
+
"""Logging level configuration."""
|
|
51
|
+
|
|
52
|
+
app_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "DEBUG"
|
|
53
|
+
lib_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "WARNING"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Pipeline (top-level)
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PipelineConfig(BaseModel):
|
|
62
|
+
"""Top-level pipeline configuration.
|
|
63
|
+
|
|
64
|
+
All sections use a define-once, reference-by-name pattern.
|
|
65
|
+
Sources compose datasets with optional selections; extractors
|
|
66
|
+
compose model type/params with optional preprocessors.
|
|
67
|
+
Tasks reference workflows, sources, and extractors by name.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Logging
|
|
71
|
+
logging: LoggingConfig | None = None
|
|
72
|
+
|
|
73
|
+
# Named resource pools
|
|
74
|
+
datasets: Sequence[DatasetConfig | DatasetProtocolConfig] | None = None
|
|
75
|
+
preprocessors: Sequence[PreprocessorConfig] | None = None
|
|
76
|
+
selections: Sequence[SelectionConfig] | None = None
|
|
77
|
+
|
|
78
|
+
# Composition layers
|
|
79
|
+
sources: Sequence[SourceConfig] | None = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
description="Named source definitions (dataset + optional selection)",
|
|
82
|
+
)
|
|
83
|
+
extractors: Sequence[ExtractorConfig] | None = Field(
|
|
84
|
+
default=None,
|
|
85
|
+
description="Named extractor definitions (model type + params + optional preprocessor + batch_size)",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Execution
|
|
89
|
+
workflows: Sequence[WorkflowConfig] | None = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
description="Named workflow configurations (type + params), referenced by tasks",
|
|
92
|
+
)
|
|
93
|
+
tasks: Sequence[TaskConfig] | None = None
|
|
94
|
+
|
|
95
|
+
@model_validator(mode="after")
|
|
96
|
+
def _check_unique_names(self) -> "PipelineConfig":
|
|
97
|
+
"""Raise if any section contains duplicate names."""
|
|
98
|
+
sections: dict[str, Sequence | None] = {
|
|
99
|
+
"datasets": self.datasets,
|
|
100
|
+
"preprocessors": self.preprocessors,
|
|
101
|
+
"selections": self.selections,
|
|
102
|
+
"sources": self.sources,
|
|
103
|
+
"extractors": self.extractors,
|
|
104
|
+
"workflows": self.workflows,
|
|
105
|
+
"tasks": self.tasks,
|
|
106
|
+
}
|
|
107
|
+
for section_name, items in sections.items():
|
|
108
|
+
if items is None:
|
|
109
|
+
continue
|
|
110
|
+
seen: set[str] = set()
|
|
111
|
+
for item in items:
|
|
112
|
+
if item.name in seen:
|
|
113
|
+
raise ValueError(f"Duplicate name '{item.name}' in {section_name}")
|
|
114
|
+
seen.add(item.name)
|
|
115
|
+
return self
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Config path validation and relativization utilities.
|
|
2
|
+
|
|
3
|
+
These are separated from ``_loader`` to avoid circular imports — the
|
|
4
|
+
schema modules (``_dataset``, ``_extractor``) need them at import time,
|
|
5
|
+
while ``_loader`` depends on ``_models`` which depends on schemas.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path, PurePosixPath
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"relativize_to_data_dir",
|
|
13
|
+
"validate_config_path",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_config_path(value: str) -> str:
|
|
18
|
+
"""Validate that a config path is relative and stays under the data root.
|
|
19
|
+
|
|
20
|
+
Config paths (dataset ``path``, extractor ``model_path``) must be
|
|
21
|
+
stored as relative paths so that configs remain portable across
|
|
22
|
+
machines and into containers where the data root is mounted at an
|
|
23
|
+
arbitrary location (e.g. ``/dataeval``).
|
|
24
|
+
|
|
25
|
+
Raises
|
|
26
|
+
------
|
|
27
|
+
ValueError
|
|
28
|
+
If *value* is an absolute path or escapes upward via ``..``.
|
|
29
|
+
"""
|
|
30
|
+
if not value:
|
|
31
|
+
raise ValueError("Path must not be empty")
|
|
32
|
+
|
|
33
|
+
p = PurePosixPath(value)
|
|
34
|
+
if p.is_absolute():
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Config paths must be relative (got absolute path '{value}'). "
|
|
37
|
+
"Use a path relative to the data root instead."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Normalize and check for upward escape via ".."
|
|
41
|
+
normalized = os.path.normpath(value)
|
|
42
|
+
if normalized.startswith(".."):
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Config path '{value}' escapes the data root directory. "
|
|
45
|
+
"All paths must resolve to locations under the data root."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def relativize_to_data_dir(absolute_path: str | Path, data_dir: Path) -> str:
|
|
52
|
+
"""Convert an absolute path to a relative path under *data_dir*.
|
|
53
|
+
|
|
54
|
+
Used by the TUI browse to convert OS-absolute paths into portable
|
|
55
|
+
relative paths suitable for config storage.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
absolute_path : str | Path
|
|
60
|
+
The absolute path to relativize.
|
|
61
|
+
data_dir : Path
|
|
62
|
+
The data root to relativize against.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
str
|
|
67
|
+
A relative path string under *data_dir*.
|
|
68
|
+
|
|
69
|
+
Raises
|
|
70
|
+
------
|
|
71
|
+
ValueError
|
|
72
|
+
If *absolute_path* is not under *data_dir*.
|
|
73
|
+
"""
|
|
74
|
+
p = Path(absolute_path).resolve()
|
|
75
|
+
root = data_dir.resolve()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
rel = p.relative_to(root)
|
|
79
|
+
except ValueError:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Path '{absolute_path}' is not under the data root '{root}'. "
|
|
82
|
+
"Config paths must point to locations inside the data directory."
|
|
83
|
+
) from None
|
|
84
|
+
|
|
85
|
+
return str(rel)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Schema catalog — concrete types and discriminated-union aliases.
|
|
2
|
+
|
|
3
|
+
Private ``_*.py`` modules define the concrete schema classes. This
|
|
4
|
+
``__init__`` re-exports them and defines the discriminated-union type
|
|
5
|
+
aliases (``DatasetConfig``, ``ExtractorConfig``, ``WorkflowConfig``)
|
|
6
|
+
consumed by :class:`~dataeval_flow.config._models.PipelineConfig`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from dataeval_flow.config.schemas._dataset import (
|
|
14
|
+
CocoDatasetConfig,
|
|
15
|
+
DatasetProtocolConfig,
|
|
16
|
+
HuggingFaceDatasetConfig,
|
|
17
|
+
ImageFolderDatasetConfig,
|
|
18
|
+
YoloDatasetConfig,
|
|
19
|
+
)
|
|
20
|
+
from dataeval_flow.config.schemas._extractor import (
|
|
21
|
+
BoVWExtractorConfig,
|
|
22
|
+
FlattenExtractorConfig,
|
|
23
|
+
OnnxExtractorConfig,
|
|
24
|
+
TorchExtractorConfig,
|
|
25
|
+
UncertaintyExtractorConfig,
|
|
26
|
+
)
|
|
27
|
+
from dataeval_flow.config.schemas._metadata import ResultMetadata
|
|
28
|
+
from dataeval_flow.config.schemas._preprocessor import PreprocessorConfig
|
|
29
|
+
from dataeval_flow.config.schemas._selection import SelectionConfig, SelectionStep
|
|
30
|
+
from dataeval_flow.config.schemas._task import (
|
|
31
|
+
AutoBinMethod,
|
|
32
|
+
DataAnalysisTaskConfig,
|
|
33
|
+
DataCleaningTaskConfig,
|
|
34
|
+
DataPrioritizationTaskConfig,
|
|
35
|
+
DataSplittingTaskConfig,
|
|
36
|
+
DriftMonitoringTaskConfig,
|
|
37
|
+
OODDetectionTaskConfig,
|
|
38
|
+
TaskConfig,
|
|
39
|
+
)
|
|
40
|
+
from dataeval_flow.config.schemas._workflow import (
|
|
41
|
+
DataAnalysisWorkflowConfig,
|
|
42
|
+
DataCleaningWorkflowConfig,
|
|
43
|
+
DataPrioritizationWorkflowConfig,
|
|
44
|
+
DataSplittingWorkflowConfig,
|
|
45
|
+
DriftMonitoringWorkflowConfig,
|
|
46
|
+
OODDetectionWorkflowConfig,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# -- discriminated-union aliases (internal) ---------------------------------
|
|
50
|
+
|
|
51
|
+
DatasetConfig = Annotated[
|
|
52
|
+
HuggingFaceDatasetConfig | ImageFolderDatasetConfig | CocoDatasetConfig | YoloDatasetConfig,
|
|
53
|
+
Field(discriminator="format"),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
ExtractorConfig = Annotated[
|
|
57
|
+
OnnxExtractorConfig
|
|
58
|
+
| BoVWExtractorConfig
|
|
59
|
+
| FlattenExtractorConfig
|
|
60
|
+
| TorchExtractorConfig
|
|
61
|
+
| UncertaintyExtractorConfig,
|
|
62
|
+
Field(discriminator="model"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
WorkflowConfig = Annotated[
|
|
66
|
+
DataAnalysisWorkflowConfig
|
|
67
|
+
| DataCleaningWorkflowConfig
|
|
68
|
+
| DataPrioritizationWorkflowConfig
|
|
69
|
+
| DataSplittingWorkflowConfig
|
|
70
|
+
| DriftMonitoringWorkflowConfig
|
|
71
|
+
| OODDetectionWorkflowConfig,
|
|
72
|
+
Field(discriminator="type"),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
# Dataset
|
|
77
|
+
"CocoDatasetConfig",
|
|
78
|
+
"DatasetConfig",
|
|
79
|
+
"DatasetProtocolConfig",
|
|
80
|
+
"HuggingFaceDatasetConfig",
|
|
81
|
+
"ImageFolderDatasetConfig",
|
|
82
|
+
"YoloDatasetConfig",
|
|
83
|
+
# Extractor
|
|
84
|
+
"BoVWExtractorConfig",
|
|
85
|
+
"ExtractorConfig",
|
|
86
|
+
"FlattenExtractorConfig",
|
|
87
|
+
"OnnxExtractorConfig",
|
|
88
|
+
"TorchExtractorConfig",
|
|
89
|
+
"UncertaintyExtractorConfig",
|
|
90
|
+
# Workflow
|
|
91
|
+
"DataAnalysisWorkflowConfig",
|
|
92
|
+
"DataCleaningWorkflowConfig",
|
|
93
|
+
"DataPrioritizationWorkflowConfig",
|
|
94
|
+
"DataSplittingWorkflowConfig",
|
|
95
|
+
"DriftMonitoringWorkflowConfig",
|
|
96
|
+
"OODDetectionWorkflowConfig",
|
|
97
|
+
"WorkflowConfig",
|
|
98
|
+
# Task
|
|
99
|
+
"AutoBinMethod",
|
|
100
|
+
"DataAnalysisTaskConfig",
|
|
101
|
+
"DataCleaningTaskConfig",
|
|
102
|
+
"DataPrioritizationTaskConfig",
|
|
103
|
+
"DataSplittingTaskConfig",
|
|
104
|
+
"DriftMonitoringTaskConfig",
|
|
105
|
+
"OODDetectionTaskConfig",
|
|
106
|
+
"TaskConfig",
|
|
107
|
+
# Other
|
|
108
|
+
"PreprocessorConfig",
|
|
109
|
+
"ResultMetadata",
|
|
110
|
+
"SelectionConfig",
|
|
111
|
+
"SelectionStep",
|
|
112
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Dataset configuration schemas — one class per format."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, field_validator
|
|
6
|
+
|
|
7
|
+
from dataeval_flow.config._paths import validate_config_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _DatasetConfigBase(BaseModel):
|
|
11
|
+
"""Common fields shared by all dataset formats."""
|
|
12
|
+
|
|
13
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
|
14
|
+
|
|
15
|
+
format: Any
|
|
16
|
+
name: str
|
|
17
|
+
path: str
|
|
18
|
+
|
|
19
|
+
@field_validator("path")
|
|
20
|
+
@classmethod
|
|
21
|
+
def _path_must_be_relative(cls, v: str) -> str:
|
|
22
|
+
return validate_config_path(v)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HuggingFaceDatasetConfig(_DatasetConfigBase):
|
|
26
|
+
"""Dataset config for HuggingFace format.
|
|
27
|
+
|
|
28
|
+
YAML example::
|
|
29
|
+
|
|
30
|
+
datasets:
|
|
31
|
+
- name: cifar10_train
|
|
32
|
+
format: huggingface
|
|
33
|
+
path: ./cifar10
|
|
34
|
+
split: train
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
format: Literal["huggingface"] = "huggingface"
|
|
38
|
+
split: str | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ImageFolderDatasetConfig(_DatasetConfigBase):
|
|
42
|
+
"""Dataset config for image_folder format.
|
|
43
|
+
|
|
44
|
+
YAML example::
|
|
45
|
+
|
|
46
|
+
datasets:
|
|
47
|
+
- name: photos
|
|
48
|
+
format: image_folder
|
|
49
|
+
path: photos
|
|
50
|
+
recursive: true
|
|
51
|
+
infer_labels: true
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
format: Literal["image_folder"] = "image_folder"
|
|
55
|
+
recursive: bool = False
|
|
56
|
+
infer_labels: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CocoDatasetConfig(_DatasetConfigBase):
|
|
60
|
+
"""Dataset config for COCO format.
|
|
61
|
+
|
|
62
|
+
YAML example::
|
|
63
|
+
|
|
64
|
+
datasets:
|
|
65
|
+
- name: coco_train
|
|
66
|
+
format: coco
|
|
67
|
+
path: coco
|
|
68
|
+
annotations_file: instances_train.json
|
|
69
|
+
images_dir: train2017
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
format: Literal["coco"] = "coco"
|
|
73
|
+
annotations_file: str | None = None
|
|
74
|
+
images_dir: str | None = None
|
|
75
|
+
classes_file: str | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class YoloDatasetConfig(_DatasetConfigBase):
|
|
79
|
+
"""Dataset config for YOLO format.
|
|
80
|
+
|
|
81
|
+
YAML example::
|
|
82
|
+
|
|
83
|
+
datasets:
|
|
84
|
+
- name: yolo_train
|
|
85
|
+
format: yolo
|
|
86
|
+
path: yolo
|
|
87
|
+
images_dir: images
|
|
88
|
+
labels_dir: labels
|
|
89
|
+
classes_file: classes.txt
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
format: Literal["yolo"] = "yolo"
|
|
93
|
+
images_dir: str | None = None
|
|
94
|
+
labels_dir: str | None = None
|
|
95
|
+
classes_file: str | None = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DatasetProtocolConfig(BaseModel):
|
|
99
|
+
"""Dataset Configuration schema for an in-memory dataset.
|
|
100
|
+
|
|
101
|
+
Not serializable — for programmatic use only. Cannot be loaded from
|
|
102
|
+
YAML/JSON config files or edited in the builder UI.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
serializable: ClassVar[bool] = False
|
|
106
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
|
|
107
|
+
|
|
108
|
+
name: str
|
|
109
|
+
format: Literal["maite", "torchvision"] = "maite"
|
|
110
|
+
dataset: Any
|
|
111
|
+
version: str = "1"
|