mlpipe-cli 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.
Files changed (46) hide show
  1. mlpipe/__init__.py +43 -0
  2. mlpipe/__main__.py +6 -0
  3. mlpipe/artifacts/__init__.py +23 -0
  4. mlpipe/artifacts/manager.py +246 -0
  5. mlpipe/artifacts/serialization.py +67 -0
  6. mlpipe/cli/__init__.py +5 -0
  7. mlpipe/cli/main.py +667 -0
  8. mlpipe/core/__init__.py +35 -0
  9. mlpipe/core/config.py +76 -0
  10. mlpipe/core/exceptions.py +65 -0
  11. mlpipe/core/pipeline.py +435 -0
  12. mlpipe/core/result.py +50 -0
  13. mlpipe/data/__init__.py +20 -0
  14. mlpipe/data/ingestion.py +138 -0
  15. mlpipe/data/profiling.py +227 -0
  16. mlpipe/data/splitting.py +130 -0
  17. mlpipe/data/validation.py +248 -0
  18. mlpipe/evaluation/__init__.py +11 -0
  19. mlpipe/evaluation/evaluator.py +146 -0
  20. mlpipe/evaluation/metrics.py +53 -0
  21. mlpipe/explainability/__init__.py +5 -0
  22. mlpipe/explainability/importance.py +65 -0
  23. mlpipe/models/__init__.py +13 -0
  24. mlpipe/models/classification.py +156 -0
  25. mlpipe/models/registry.py +32 -0
  26. mlpipe/models/regression.py +126 -0
  27. mlpipe/models/selection.py +24 -0
  28. mlpipe/preprocessing/__init__.py +21 -0
  29. mlpipe/preprocessing/builder.py +163 -0
  30. mlpipe/preprocessing/categorical.py +17 -0
  31. mlpipe/preprocessing/datetime.py +55 -0
  32. mlpipe/preprocessing/numeric.py +17 -0
  33. mlpipe/tuning/__init__.py +10 -0
  34. mlpipe/tuning/search.py +140 -0
  35. mlpipe/tuning/spaces.py +11 -0
  36. mlpipe/utils/__init__.py +13 -0
  37. mlpipe/utils/hashing.py +15 -0
  38. mlpipe/utils/logging.py +37 -0
  39. mlpipe/utils/timing.py +33 -0
  40. mlpipe/version.py +3 -0
  41. mlpipe_cli-0.1.0.dist-info/METADATA +264 -0
  42. mlpipe_cli-0.1.0.dist-info/RECORD +46 -0
  43. mlpipe_cli-0.1.0.dist-info/WHEEL +5 -0
  44. mlpipe_cli-0.1.0.dist-info/entry_points.txt +2 -0
  45. mlpipe_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  46. mlpipe_cli-0.1.0.dist-info/top_level.txt +1 -0
mlpipe/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ MLPipe: Automated Machine Learning Library and Terminal CLI.
3
+ """
4
+
5
+ from mlpipe.core.config import PipelineConfig, TaskType, TrainingMode
6
+ from mlpipe.core.exceptions import (
7
+ ArtifactError,
8
+ ConfigurationError,
9
+ DatasetError,
10
+ EvaluationError,
11
+ MLPipeError,
12
+ PipelineError,
13
+ PredictionError,
14
+ PreprocessingError,
15
+ TrainingError,
16
+ ValidationError,
17
+ )
18
+ from mlpipe.core.pipeline import Pipeline
19
+ from mlpipe.core.result import PipelineResult
20
+ from mlpipe.data.profiling import DatasetProfile
21
+ from mlpipe.data.validation import ValidationReport
22
+ from mlpipe.version import __version__
23
+
24
+ __all__ = [
25
+ "Pipeline",
26
+ "PipelineResult",
27
+ "DatasetProfile",
28
+ "ValidationReport",
29
+ "PipelineConfig",
30
+ "TaskType",
31
+ "TrainingMode",
32
+ "MLPipeError",
33
+ "DatasetError",
34
+ "ValidationError",
35
+ "PreprocessingError",
36
+ "TrainingError",
37
+ "EvaluationError",
38
+ "ArtifactError",
39
+ "ConfigurationError",
40
+ "PredictionError",
41
+ "PipelineError",
42
+ "__version__",
43
+ ]
mlpipe/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """CLI entrypoint when running python -m mlpipe."""
2
+
3
+ from mlpipe.cli.main import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,23 @@
1
+ """Artifact management and serialization for MLPipe."""
2
+
3
+ from mlpipe.artifacts.manager import (
4
+ ArtifactManager,
5
+ inspect_run_directory,
6
+ load_pipeline_artifact,
7
+ )
8
+ from mlpipe.artifacts.serialization import (
9
+ load_joblib,
10
+ load_json,
11
+ save_joblib,
12
+ save_json,
13
+ )
14
+
15
+ __all__ = [
16
+ "ArtifactManager",
17
+ "inspect_run_directory",
18
+ "load_pipeline_artifact",
19
+ "load_joblib",
20
+ "load_json",
21
+ "save_joblib",
22
+ "save_json",
23
+ ]
@@ -0,0 +1,246 @@
1
+ """
2
+ Artifact management and inspection for MLPipe runs.
3
+
4
+ Creates reproducible artifact bundles containing model, pipeline, metrics,
5
+ metadata, and textual summaries.
6
+ """
7
+
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+ import platform
11
+ import sys
12
+ from typing import Any, Dict, List, Optional, Union
13
+ import uuid
14
+
15
+ import numpy as np
16
+ import pandas as pd
17
+ from sklearn.pipeline import Pipeline
18
+
19
+ from mlpipe.artifacts.serialization import load_joblib, load_json, save_joblib, save_json
20
+ from mlpipe.core.exceptions import ArtifactError
21
+ from mlpipe.version import __version__
22
+
23
+
24
+ class ArtifactManager:
25
+ """Manages the creation, saving, and inspection of MLPipe run artifacts."""
26
+
27
+ def __init__(self, base_output_dir: Union[str, Path] = "./mlpipe_runs"):
28
+ self.base_output_dir = Path(base_output_dir)
29
+
30
+ def generate_run_id(self) -> str:
31
+ """Create a deterministic-friendly, timestamped unique run ID."""
32
+ ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
33
+ uid = uuid.uuid4().hex[:6]
34
+ return f"run_{ts}_{uid}"
35
+
36
+ def save_run_artifacts(
37
+ self,
38
+ run_id: str,
39
+ pipeline: Pipeline,
40
+ dataset_summary: Dict[str, Any],
41
+ target_column: str,
42
+ task_type: str,
43
+ training_mode: str,
44
+ primary_metric: str,
45
+ best_model_name: str,
46
+ best_cv_score: float,
47
+ test_score: float,
48
+ test_metrics: Dict[str, Any],
49
+ leaderboard: List[Dict[str, Any]],
50
+ feature_importance: List[Dict[str, Any]],
51
+ elapsed_time_s: float,
52
+ random_seed: int = 42,
53
+ train_df: Optional[pd.DataFrame] = None,
54
+ test_df: Optional[pd.DataFrame] = None,
55
+ test_predictions_df: Optional[pd.DataFrame] = None,
56
+ ) -> Path:
57
+ """Save all artifacts for a completed pipeline run."""
58
+ run_dir = self.base_output_dir / run_id
59
+ run_dir.mkdir(parents=True, exist_ok=True)
60
+
61
+ # 0. Save data splits if provided
62
+ if train_df is not None:
63
+ train_df.to_csv(run_dir / "train.csv", index=False)
64
+ if test_df is not None:
65
+ test_df.to_csv(run_dir / "test.csv", index=False)
66
+ if test_predictions_df is not None:
67
+ test_predictions_df.to_csv(run_dir / "test_predictions.csv", index=False)
68
+
69
+ # 1. Save complete pipeline (reusable end-to-end)
70
+ save_joblib(pipeline, run_dir / "pipeline.joblib")
71
+
72
+ # 2. Save fitted estimator alone
73
+ if "estimator" in pipeline.named_steps:
74
+ save_joblib(pipeline.named_steps["estimator"], run_dir / "model.joblib")
75
+
76
+ # 3. Save metrics
77
+ all_metrics = {
78
+ "primary_metric": primary_metric,
79
+ "best_cv_score": best_cv_score,
80
+ "test_score": test_score,
81
+ "test_metrics": test_metrics,
82
+ }
83
+ save_json(all_metrics, run_dir / "metrics.json")
84
+
85
+ # 4. Save leaderboard
86
+ # Strip fitted objects if any before serializing
87
+ clean_leaderboard = []
88
+ for row in leaderboard:
89
+ r = dict(row)
90
+ if "pipeline" in r:
91
+ del r["pipeline"]
92
+ clean_leaderboard.append(r)
93
+ save_json(clean_leaderboard, run_dir / "leaderboard.json")
94
+
95
+ # 5. Save feature importance
96
+ save_json(feature_importance, run_dir / "feature_importance.json")
97
+
98
+ # 6. Save metadata
99
+ metadata = {
100
+ "run_id": run_id,
101
+ "timestamp": datetime.now(timezone.utc).isoformat(),
102
+ "mlpipe_version": __version__,
103
+ "python_version": sys.version,
104
+ "platform": platform.platform(),
105
+ "dataset": dataset_summary,
106
+ "target_column": target_column,
107
+ "task_type": task_type,
108
+ "training_mode": training_mode,
109
+ "primary_metric": primary_metric,
110
+ "best_model": best_model_name,
111
+ "best_cv_score": best_cv_score,
112
+ "test_score": test_score,
113
+ "random_seed": random_seed,
114
+ "elapsed_time_s": elapsed_time_s,
115
+ "splits": {
116
+ "train_samples": len(train_df) if train_df is not None else None,
117
+ "test_samples": len(test_df) if test_df is not None else None,
118
+ "train_file": "train.csv" if train_df is not None else None,
119
+ "test_file": "test.csv" if test_df is not None else None,
120
+ "predictions_file": "test_predictions.csv" if test_predictions_df is not None else None,
121
+ },
122
+ }
123
+ save_json(metadata, run_dir / "metadata.json")
124
+
125
+ # 7. Generate text report
126
+ report_text = self._build_text_report(metadata, clean_leaderboard, test_metrics, feature_importance)
127
+ with open(run_dir / "report.txt", "w", encoding="utf-8") as f:
128
+ f.write(report_text)
129
+
130
+ return run_dir
131
+
132
+ def _build_text_report(
133
+ self,
134
+ meta: Dict[str, Any],
135
+ leaderboard: List[Dict[str, Any]],
136
+ test_metrics: Dict[str, Any],
137
+ feature_importance: List[Dict[str, Any]],
138
+ ) -> str:
139
+ lines = [
140
+ f"==================================================",
141
+ f"MLPipe Run Report - {meta['run_id']}",
142
+ f"==================================================",
143
+ f"Timestamp: {meta['timestamp']}",
144
+ f"MLPipe Version: {meta['mlpipe_version']}",
145
+ f"Dataset: {meta['dataset'].get('filename', 'unknown')} ({meta['dataset'].get('rows')} rows, {meta['dataset'].get('columns')} cols)",
146
+ f"Dataset SHA256: {meta['dataset'].get('sha256', 'unknown')}",
147
+ f"Target Column: {meta['target_column']}",
148
+ f"Task Type: {meta['task_type'].capitalize()}",
149
+ f"Training Mode: {meta['training_mode']}",
150
+ f"Seed: {meta['random_seed']}",
151
+ f"Elapsed Time: {meta['elapsed_time_s']:.2f}s",
152
+ f"",
153
+ f"BEST MODEL",
154
+ f"--------------------------------------------------",
155
+ f"Model: {meta['best_model']}",
156
+ f"Primary Metric: {meta['primary_metric']}",
157
+ f"Best CV Score: {meta['best_cv_score']}",
158
+ f"Test Score: {meta['test_score']}",
159
+ f"",
160
+ f"MODEL LEADERBOARD",
161
+ f"--------------------------------------------------",
162
+ ]
163
+ for row in leaderboard:
164
+ status = row.get("status", "unknown")
165
+ cv = row.get("cv_score", "N/A")
166
+ test = row.get("test_score", "N/A")
167
+ time_s = row.get("training_time_s", "N/A")
168
+ lines.append(f"{row['model']:<25} | CV: {str(cv):<8} | Test: {str(test):<8} | {time_s}s | {status}")
169
+
170
+ lines.extend([
171
+ f"",
172
+ f"TEST EVALUATION METRICS",
173
+ f"--------------------------------------------------",
174
+ ])
175
+ for k, v in test_metrics.items():
176
+ if k != "confusion_matrix":
177
+ lines.append(f"{k:<20}: {v}")
178
+
179
+ splits = meta.get("splits", {})
180
+ if splits and splits.get("train_samples") is not None:
181
+ lines.extend([
182
+ f"",
183
+ f"DATA SPLITS & HOLD-OUT TEST SET",
184
+ f"--------------------------------------------------",
185
+ f"Training Samples: {splits.get('train_samples')} ({splits.get('train_file')})",
186
+ f"Testing Samples: {splits.get('test_samples')} ({splits.get('test_file')})",
187
+ f"Test Predictions: {splits.get('predictions_file')}",
188
+ ])
189
+
190
+ if feature_importance:
191
+ lines.extend([
192
+ f"",
193
+ f"TOP FEATURE IMPORTANCES",
194
+ f"--------------------------------------------------",
195
+ ])
196
+ for item in feature_importance[:10]:
197
+ lines.append(f"{item['feature']:<30}: {item['importance']}")
198
+
199
+ return "\n".join(lines)
200
+
201
+
202
+ def load_pipeline_artifact(path: Union[str, Path]) -> Pipeline:
203
+ """
204
+ Load a fitted pipeline from either a run directory or direct joblib file path.
205
+ """
206
+ p = Path(path).resolve()
207
+ if p.is_dir():
208
+ candidate_file = p / "pipeline.joblib"
209
+ if candidate_file.exists():
210
+ return load_joblib(candidate_file)
211
+ raise ArtifactError(
212
+ f"No 'pipeline.joblib' found in directory '{p}'.",
213
+ "Provide the path to the pipeline.joblib file or a valid MLPipe run directory."
214
+ )
215
+
216
+ if not p.exists():
217
+ raise ArtifactError(f"Pipeline file '{p}' does not exist.")
218
+
219
+ return load_joblib(p)
220
+
221
+
222
+ def inspect_run_directory(run_dir: Union[str, Path]) -> Dict[str, Any]:
223
+ """Inspect and return metadata and artifacts summary from a run directory."""
224
+ dir_path = Path(run_dir).resolve()
225
+ if not dir_path.is_dir():
226
+ raise ArtifactError(f"Path '{dir_path}' is not a directory.")
227
+
228
+ meta_file = dir_path / "metadata.json"
229
+ if not meta_file.exists():
230
+ raise ArtifactError(
231
+ f"Directory '{dir_path.name}' does not contain metadata.json.",
232
+ "Verify that this is a completed MLPipe run directory."
233
+ )
234
+
235
+ metadata = load_json(meta_file)
236
+
237
+ # Check available artifacts
238
+ available_artifacts = [f.name for f in dir_path.iterdir() if f.is_file()]
239
+ metadata["artifacts"] = available_artifacts
240
+
241
+ # Load metrics if available
242
+ metrics_file = dir_path / "metrics.json"
243
+ if metrics_file.exists():
244
+ metadata["detailed_metrics"] = load_json(metrics_file)
245
+
246
+ return metadata
@@ -0,0 +1,67 @@
1
+ """Serialization utilities using joblib and JSON."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Union
6
+
7
+ import joblib
8
+
9
+ from mlpipe.core.exceptions import ArtifactError
10
+
11
+
12
+ def save_joblib(obj: Any, path: Union[str, Path]) -> Path:
13
+ """Save a Python object using joblib."""
14
+ path = Path(path)
15
+ path.parent.mkdir(parents=True, exist_ok=True)
16
+ try:
17
+ joblib.dump(obj, path)
18
+ return path
19
+ except Exception as e:
20
+ raise ArtifactError(
21
+ f"Failed to serialize object to '{path}': {e}",
22
+ "Check disk permissions and available space."
23
+ )
24
+
25
+
26
+ def load_joblib(path: Union[str, Path]) -> Any:
27
+ """Load a Python object from a joblib file."""
28
+ path = Path(path)
29
+ if not path.exists():
30
+ raise ArtifactError(
31
+ f"Joblib file not found at '{path}'.",
32
+ "Verify that the file path is correct."
33
+ )
34
+ try:
35
+ return joblib.load(path)
36
+ except Exception as e:
37
+ raise ArtifactError(
38
+ f"Failed to load object from '{path}': {e}",
39
+ "Ensure the file was generated by a compatible version of joblib and scikit-learn."
40
+ )
41
+
42
+
43
+ def save_json(data: Any, path: Union[str, Path], indent: int = 2) -> Path:
44
+ """Save data structure to formatted JSON."""
45
+ path = Path(path)
46
+ path.parent.mkdir(parents=True, exist_ok=True)
47
+ try:
48
+ with open(path, "w", encoding="utf-8") as f:
49
+ json.dump(data, f, indent=indent, default=str)
50
+ return path
51
+ except Exception as e:
52
+ raise ArtifactError(
53
+ f"Failed to write JSON artifact to '{path}': {e}",
54
+ "Verify write permissions."
55
+ )
56
+
57
+
58
+ def load_json(path: Union[str, Path]) -> Any:
59
+ """Load data from a JSON file."""
60
+ path = Path(path)
61
+ if not path.exists():
62
+ raise ArtifactError(f"JSON artifact not found at '{path}'.")
63
+ try:
64
+ with open(path, "r", encoding="utf-8") as f:
65
+ return json.load(f)
66
+ except Exception as e:
67
+ raise ArtifactError(f"Failed to parse JSON artifact from '{path}': {e}")
mlpipe/cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """CLI module for MLPipe."""
2
+
3
+ from mlpipe.cli.main import app
4
+
5
+ __all__ = ["app"]