flowyml 1.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 (159) hide show
  1. flowyml/__init__.py +207 -0
  2. flowyml/assets/__init__.py +22 -0
  3. flowyml/assets/artifact.py +40 -0
  4. flowyml/assets/base.py +209 -0
  5. flowyml/assets/dataset.py +100 -0
  6. flowyml/assets/featureset.py +301 -0
  7. flowyml/assets/metrics.py +104 -0
  8. flowyml/assets/model.py +82 -0
  9. flowyml/assets/registry.py +157 -0
  10. flowyml/assets/report.py +315 -0
  11. flowyml/cli/__init__.py +5 -0
  12. flowyml/cli/experiment.py +232 -0
  13. flowyml/cli/init.py +256 -0
  14. flowyml/cli/main.py +327 -0
  15. flowyml/cli/run.py +75 -0
  16. flowyml/cli/stack_cli.py +532 -0
  17. flowyml/cli/ui.py +33 -0
  18. flowyml/core/__init__.py +68 -0
  19. flowyml/core/advanced_cache.py +274 -0
  20. flowyml/core/approval.py +64 -0
  21. flowyml/core/cache.py +203 -0
  22. flowyml/core/checkpoint.py +148 -0
  23. flowyml/core/conditional.py +373 -0
  24. flowyml/core/context.py +155 -0
  25. flowyml/core/error_handling.py +419 -0
  26. flowyml/core/executor.py +354 -0
  27. flowyml/core/graph.py +185 -0
  28. flowyml/core/parallel.py +452 -0
  29. flowyml/core/pipeline.py +764 -0
  30. flowyml/core/project.py +253 -0
  31. flowyml/core/resources.py +424 -0
  32. flowyml/core/scheduler.py +630 -0
  33. flowyml/core/scheduler_config.py +32 -0
  34. flowyml/core/step.py +201 -0
  35. flowyml/core/step_grouping.py +292 -0
  36. flowyml/core/templates.py +226 -0
  37. flowyml/core/versioning.py +217 -0
  38. flowyml/integrations/__init__.py +1 -0
  39. flowyml/integrations/keras.py +134 -0
  40. flowyml/monitoring/__init__.py +1 -0
  41. flowyml/monitoring/alerts.py +57 -0
  42. flowyml/monitoring/data.py +102 -0
  43. flowyml/monitoring/llm.py +160 -0
  44. flowyml/monitoring/monitor.py +57 -0
  45. flowyml/monitoring/notifications.py +246 -0
  46. flowyml/registry/__init__.py +5 -0
  47. flowyml/registry/model_registry.py +491 -0
  48. flowyml/registry/pipeline_registry.py +55 -0
  49. flowyml/stacks/__init__.py +27 -0
  50. flowyml/stacks/base.py +77 -0
  51. flowyml/stacks/bridge.py +288 -0
  52. flowyml/stacks/components.py +155 -0
  53. flowyml/stacks/gcp.py +499 -0
  54. flowyml/stacks/local.py +112 -0
  55. flowyml/stacks/migration.py +97 -0
  56. flowyml/stacks/plugin_config.py +78 -0
  57. flowyml/stacks/plugins.py +401 -0
  58. flowyml/stacks/registry.py +226 -0
  59. flowyml/storage/__init__.py +26 -0
  60. flowyml/storage/artifacts.py +246 -0
  61. flowyml/storage/materializers/__init__.py +20 -0
  62. flowyml/storage/materializers/base.py +133 -0
  63. flowyml/storage/materializers/keras.py +185 -0
  64. flowyml/storage/materializers/numpy.py +94 -0
  65. flowyml/storage/materializers/pandas.py +142 -0
  66. flowyml/storage/materializers/pytorch.py +135 -0
  67. flowyml/storage/materializers/sklearn.py +110 -0
  68. flowyml/storage/materializers/tensorflow.py +152 -0
  69. flowyml/storage/metadata.py +931 -0
  70. flowyml/tracking/__init__.py +1 -0
  71. flowyml/tracking/experiment.py +211 -0
  72. flowyml/tracking/leaderboard.py +191 -0
  73. flowyml/tracking/runs.py +145 -0
  74. flowyml/ui/__init__.py +15 -0
  75. flowyml/ui/backend/Dockerfile +31 -0
  76. flowyml/ui/backend/__init__.py +0 -0
  77. flowyml/ui/backend/auth.py +163 -0
  78. flowyml/ui/backend/main.py +187 -0
  79. flowyml/ui/backend/routers/__init__.py +0 -0
  80. flowyml/ui/backend/routers/assets.py +45 -0
  81. flowyml/ui/backend/routers/execution.py +179 -0
  82. flowyml/ui/backend/routers/experiments.py +49 -0
  83. flowyml/ui/backend/routers/leaderboard.py +118 -0
  84. flowyml/ui/backend/routers/notifications.py +72 -0
  85. flowyml/ui/backend/routers/pipelines.py +110 -0
  86. flowyml/ui/backend/routers/plugins.py +192 -0
  87. flowyml/ui/backend/routers/projects.py +85 -0
  88. flowyml/ui/backend/routers/runs.py +66 -0
  89. flowyml/ui/backend/routers/schedules.py +222 -0
  90. flowyml/ui/backend/routers/traces.py +84 -0
  91. flowyml/ui/frontend/Dockerfile +20 -0
  92. flowyml/ui/frontend/README.md +315 -0
  93. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
  94. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
  95. flowyml/ui/frontend/dist/index.html +16 -0
  96. flowyml/ui/frontend/index.html +15 -0
  97. flowyml/ui/frontend/nginx.conf +26 -0
  98. flowyml/ui/frontend/package-lock.json +3545 -0
  99. flowyml/ui/frontend/package.json +33 -0
  100. flowyml/ui/frontend/postcss.config.js +6 -0
  101. flowyml/ui/frontend/src/App.jsx +21 -0
  102. flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
  103. flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
  104. flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
  105. flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
  106. flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
  107. flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
  108. flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
  109. flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
  110. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
  111. flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
  112. flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
  113. flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
  114. flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
  115. flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
  116. flowyml/ui/frontend/src/components/Layout.jsx +108 -0
  117. flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
  118. flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
  119. flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
  120. flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
  121. flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
  122. flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
  123. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
  124. flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
  125. flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
  126. flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
  127. flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
  128. flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
  129. flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
  130. flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
  131. flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
  132. flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
  133. flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
  134. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
  135. flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
  136. flowyml/ui/frontend/src/index.css +11 -0
  137. flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
  138. flowyml/ui/frontend/src/main.jsx +10 -0
  139. flowyml/ui/frontend/src/router/index.jsx +39 -0
  140. flowyml/ui/frontend/src/services/pluginService.js +90 -0
  141. flowyml/ui/frontend/src/utils/api.js +47 -0
  142. flowyml/ui/frontend/src/utils/cn.js +6 -0
  143. flowyml/ui/frontend/tailwind.config.js +31 -0
  144. flowyml/ui/frontend/vite.config.js +21 -0
  145. flowyml/ui/utils.py +77 -0
  146. flowyml/utils/__init__.py +67 -0
  147. flowyml/utils/config.py +308 -0
  148. flowyml/utils/debug.py +240 -0
  149. flowyml/utils/environment.py +346 -0
  150. flowyml/utils/git.py +319 -0
  151. flowyml/utils/logging.py +61 -0
  152. flowyml/utils/performance.py +314 -0
  153. flowyml/utils/stack_config.py +296 -0
  154. flowyml/utils/validation.py +270 -0
  155. flowyml-1.1.0.dist-info/METADATA +372 -0
  156. flowyml-1.1.0.dist-info/RECORD +159 -0
  157. flowyml-1.1.0.dist-info/WHEEL +4 -0
  158. flowyml-1.1.0.dist-info/entry_points.txt +3 -0
  159. flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1 @@
1
+ """Experiment tracking and run management."""
@@ -0,0 +1,211 @@
1
+ """Experiment Tracking - Track ML experiments and compare results."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+ from datetime import datetime
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ @dataclass
11
+ class ExperimentConfig:
12
+ """Configuration for an experiment."""
13
+
14
+ name: str
15
+ description: str
16
+ tags: dict[str, str] = field(default_factory=dict)
17
+ parameters: dict[str, Any] = field(default_factory=dict)
18
+ created_at: datetime = field(default_factory=datetime.now)
19
+
20
+ def to_dict(self) -> dict[str, Any]:
21
+ """Convert to dictionary."""
22
+ return {
23
+ "name": self.name,
24
+ "description": self.description,
25
+ "tags": self.tags,
26
+ "parameters": self.parameters,
27
+ "created_at": self.created_at.isoformat(),
28
+ }
29
+
30
+
31
+ class Experiment:
32
+ """Experiment for tracking multiple pipeline runs.
33
+
34
+ Example:
35
+ >>> exp = Experiment(name="baseline_training", description="Baseline model training")
36
+ >>> exp.log_run(run_id, metrics={"accuracy": 0.95})
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ name: str,
42
+ description: str = "",
43
+ tags: dict[str, str] | None = None,
44
+ parameters: dict[str, Any] | None = None,
45
+ experiment_dir: str = ".flowyml/experiments",
46
+ ):
47
+ self.name = name
48
+ self.description = description
49
+ self.config = ExperimentConfig(
50
+ name=name,
51
+ description=description,
52
+ tags=tags or {},
53
+ parameters=parameters or {},
54
+ )
55
+
56
+ # Storage
57
+ self.experiment_dir = Path(experiment_dir) / name
58
+ self.experiment_dir.mkdir(parents=True, exist_ok=True)
59
+
60
+ # Metadata store for UI
61
+ from flowyml.storage.metadata import SQLiteMetadataStore
62
+
63
+ self.metadata_store = SQLiteMetadataStore()
64
+
65
+ # Save experiment to DB
66
+ self.metadata_store.save_experiment(
67
+ experiment_id=self.name,
68
+ name=self.name,
69
+ description=self.description,
70
+ tags=tags,
71
+ )
72
+
73
+ # Runs
74
+ self.runs: list[str] = [] # run IDs
75
+ self.run_metrics: dict[str, dict[str, Any]] = {}
76
+
77
+ self._load_experiment()
78
+
79
+ def log_run(
80
+ self,
81
+ run_id: str,
82
+ metrics: dict[str, Any] | None = None,
83
+ parameters: dict[str, Any] | None = None,
84
+ ) -> None:
85
+ """Log a pipeline run to this experiment.
86
+
87
+ Args:
88
+ run_id: Run identifier
89
+ metrics: Metrics from the run
90
+ parameters: Parameters used in the run
91
+ """
92
+ if run_id not in self.runs:
93
+ self.runs.append(run_id)
94
+
95
+ self.run_metrics[run_id] = {
96
+ "metrics": metrics or {},
97
+ "parameters": parameters or {},
98
+ "timestamp": datetime.now().isoformat(),
99
+ }
100
+
101
+ self._save_experiment()
102
+
103
+ # Log to DB
104
+ self.metadata_store.log_experiment_run(
105
+ experiment_id=self.name,
106
+ run_id=run_id,
107
+ metrics=metrics,
108
+ parameters=parameters,
109
+ )
110
+
111
+ def get_run_metrics(self, run_id: str) -> dict[str, Any] | None:
112
+ """Get metrics for a specific run."""
113
+ return self.run_metrics.get(run_id)
114
+
115
+ def list_runs(self) -> list[str]:
116
+ """List all runs in this experiment."""
117
+ return self.runs
118
+
119
+ def compare_runs(
120
+ self,
121
+ run_ids: list[str] | None = None,
122
+ metric: str | None = None,
123
+ ) -> dict[str, Any]:
124
+ """Compare runs in this experiment.
125
+
126
+ Args:
127
+ run_ids: Specific runs to compare (or all if None)
128
+ metric: Specific metric to compare (or all if None)
129
+
130
+ Returns:
131
+ Comparison results
132
+ """
133
+ runs_to_compare = run_ids or self.runs
134
+
135
+ comparison = {
136
+ "experiment": self.name,
137
+ "runs": {},
138
+ }
139
+
140
+ for run_id in runs_to_compare:
141
+ if run_id not in self.run_metrics:
142
+ continue
143
+
144
+ run_data = self.run_metrics[run_id]
145
+ metrics = run_data.get("metrics", {})
146
+
147
+ if metric:
148
+ comparison["runs"][run_id] = {metric: metrics.get(metric)}
149
+ else:
150
+ comparison["runs"][run_id] = metrics
151
+
152
+ return comparison
153
+
154
+ def get_best_run(self, metric: str, maximize: bool = True) -> str | None:
155
+ """Get the best run based on a metric.
156
+
157
+ Args:
158
+ metric: Metric to optimize
159
+ maximize: Whether to maximize (True) or minimize (False)
160
+
161
+ Returns:
162
+ Best run ID
163
+ """
164
+ best_run = None
165
+ best_value = float("-inf") if maximize else float("inf")
166
+
167
+ for run_id, run_data in self.run_metrics.items():
168
+ metrics = run_data.get("metrics", {})
169
+ value = metrics.get(metric)
170
+
171
+ if value is None:
172
+ continue
173
+
174
+ if maximize and value > best_value or not maximize and value < best_value:
175
+ best_value = value
176
+ best_run = run_id
177
+
178
+ return best_run
179
+
180
+ def _save_experiment(self) -> None:
181
+ """Save experiment data to disk."""
182
+ experiment_file = self.experiment_dir / "experiment.json"
183
+
184
+ data = {
185
+ "config": self.config.to_dict(),
186
+ "runs": self.runs,
187
+ "run_metrics": self.run_metrics,
188
+ }
189
+
190
+ with open(experiment_file, "w") as f:
191
+ json.dump(data, f, indent=2)
192
+
193
+ def _load_experiment(self) -> None:
194
+ """Load experiment data from disk."""
195
+ experiment_file = self.experiment_dir / "experiment.json"
196
+
197
+ if not experiment_file.exists():
198
+ return
199
+
200
+ try:
201
+ with open(experiment_file) as f:
202
+ data = json.load(f)
203
+
204
+ self.runs = data.get("runs", [])
205
+ self.run_metrics = data.get("run_metrics", {})
206
+
207
+ except Exception:
208
+ pass
209
+
210
+ def __repr__(self) -> str:
211
+ return f"Experiment(name='{self.name}', runs={len(self.runs)})"
@@ -0,0 +1,191 @@
1
+ """Model comparison and leaderboard functionality."""
2
+
3
+ from typing import Any
4
+ from dataclasses import dataclass
5
+ from flowyml.storage.metadata import SQLiteMetadataStore
6
+
7
+
8
+ @dataclass
9
+ class ModelScore:
10
+ """A model's score on a leaderboard."""
11
+
12
+ model_name: str
13
+ run_id: str
14
+ metric_value: float
15
+ timestamp: str
16
+ metadata: dict[str, Any]
17
+
18
+
19
+ class ModelLeaderboard:
20
+ """Compare and rank models based on metrics.
21
+
22
+ Examples:
23
+ >>> leaderboard = ModelLeaderboard(metric="accuracy")
24
+ >>> # Add model scores
25
+ >>> leaderboard.add_score(model_name="bert-base", run_id="run_123", score=0.92)
26
+ >>> # Get top models
27
+ >>> top_models = leaderboard.get_top(n=5)
28
+ >>> leaderboard.display()
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ metric: str,
34
+ higher_is_better: bool = True,
35
+ metadata_store: SQLiteMetadataStore | None = None,
36
+ ):
37
+ """Args:
38
+ metric: Metric to compare on
39
+ higher_is_better: Whether higher values are better
40
+ metadata_store: Optional metadata store.
41
+ """
42
+ self.metric = metric
43
+ self.higher_is_better = higher_is_better
44
+ self.metadata_store = metadata_store or SQLiteMetadataStore()
45
+
46
+ def add_score(
47
+ self,
48
+ model_name: str,
49
+ run_id: str,
50
+ score: float,
51
+ metadata: dict[str, Any] | None = None,
52
+ ) -> None:
53
+ """Add a model score to the leaderboard."""
54
+ # Save to metadata store
55
+ self.metadata_store.save_metric(
56
+ run_id=run_id,
57
+ name=f"leaderboard_{self.metric}",
58
+ value=score,
59
+ )
60
+
61
+ # Also save model info as artifact
62
+ self.metadata_store.save_artifact(
63
+ artifact_id=f"{run_id}_leaderboard",
64
+ metadata={
65
+ "name": model_name,
66
+ "type": "leaderboard_entry",
67
+ "run_id": run_id,
68
+ "value": str(score),
69
+ "metric": self.metric,
70
+ "metadata": metadata or {},
71
+ },
72
+ )
73
+
74
+ def get_top(self, n: int = 10) -> list[ModelScore]:
75
+ """Get top N models."""
76
+ # Query all leaderboard entries
77
+ artifacts = self.metadata_store.list_assets(type="leaderboard_entry")
78
+
79
+ # Filter by metric
80
+ entries = [a for a in artifacts if a.get("metric") == self.metric]
81
+
82
+ # Sort
83
+ entries.sort(
84
+ key=lambda x: float(x.get("value", 0)),
85
+ reverse=self.higher_is_better,
86
+ )
87
+
88
+ # Convert to ModelScore
89
+ scores = []
90
+ for entry in entries[:n]:
91
+ scores.append(
92
+ ModelScore(
93
+ model_name=entry.get("name"),
94
+ run_id=entry.get("run_id"),
95
+ metric_value=float(entry.get("value", 0)),
96
+ timestamp=entry.get("created_at", ""),
97
+ metadata=entry.get("metadata", {}),
98
+ ),
99
+ )
100
+
101
+ return scores
102
+
103
+ def display(self, n: int = 10) -> None:
104
+ """Display the leaderboard."""
105
+ scores = self.get_top(n)
106
+
107
+ if not scores:
108
+ return
109
+
110
+ for _i, _score in enumerate(scores, 1):
111
+ pass
112
+
113
+ def compare_models(self, run_ids: list[str]) -> dict[str, Any]:
114
+ """Compare specific models side-by-side."""
115
+ comparison = {
116
+ "metric": self.metric,
117
+ "models": [],
118
+ }
119
+
120
+ for run_id in run_ids:
121
+ # Get metrics for this run
122
+ metrics = self.metadata_store.get_metrics(run_id)
123
+ metric_val = next(
124
+ (m["value"] for m in metrics if m["name"] == self.metric),
125
+ None,
126
+ )
127
+
128
+ # Get run details
129
+ run = self.metadata_store.load_run(run_id)
130
+
131
+ comparison["models"].append(
132
+ {
133
+ "run_id": run_id,
134
+ "metric_value": metric_val,
135
+ "pipeline": run.get("pipeline_name") if run else None,
136
+ "status": run.get("status") if run else None,
137
+ },
138
+ )
139
+
140
+ # Sort by metric value
141
+ comparison["models"].sort(
142
+ key=lambda x: x.get("metric_value", 0) or 0,
143
+ reverse=self.higher_is_better,
144
+ )
145
+
146
+ return comparison
147
+
148
+
149
+ def compare_runs(
150
+ run_ids: list[str],
151
+ metrics: list[str] | None = None,
152
+ ) -> dict[str, Any]:
153
+ """Compare multiple runs across metrics.
154
+
155
+ Args:
156
+ run_ids: List of run IDs to compare
157
+ metrics: Optional list of specific metrics to compare
158
+
159
+ Returns:
160
+ Comparison dictionary
161
+ """
162
+ store = SQLiteMetadataStore()
163
+
164
+ comparison = {
165
+ "runs": {},
166
+ "metrics": {},
167
+ }
168
+
169
+ # Collect all metrics across runs
170
+ all_metric_names = set()
171
+
172
+ for run_id in run_ids:
173
+ run_metrics = store.get_metrics(run_id)
174
+ run_data = {
175
+ "metrics": {m["name"]: m["value"] for m in run_metrics},
176
+ }
177
+
178
+ all_metric_names.update(run_data["metrics"].keys())
179
+ comparison["runs"][run_id] = run_data
180
+
181
+ # Filter metrics if specified
182
+ if metrics:
183
+ all_metric_names = set(metrics)
184
+
185
+ # Organize by metric
186
+ for metric_name in all_metric_names:
187
+ comparison["metrics"][metric_name] = {
188
+ run_id: comparison["runs"][run_id]["metrics"].get(metric_name) for run_id in run_ids
189
+ }
190
+
191
+ return comparison
@@ -0,0 +1,145 @@
1
+ """Run Management - Track individual pipeline runs."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+ from datetime import datetime
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ @dataclass
11
+ class RunMetadata:
12
+ """Metadata for a pipeline run."""
13
+
14
+ run_id: str
15
+ pipeline_name: str
16
+ started_at: datetime
17
+ ended_at: datetime | None = None
18
+ status: str = "running" # running, success, failed
19
+ parameters: dict[str, Any] = field(default_factory=dict)
20
+ metrics: dict[str, Any] = field(default_factory=dict)
21
+ tags: dict[str, str] = field(default_factory=dict)
22
+
23
+ def to_dict(self) -> dict[str, Any]:
24
+ """Convert to dictionary."""
25
+ return {
26
+ "run_id": self.run_id,
27
+ "pipeline_name": self.pipeline_name,
28
+ "started_at": self.started_at.isoformat(),
29
+ "ended_at": self.ended_at.isoformat() if self.ended_at else None,
30
+ "status": self.status,
31
+ "parameters": self.parameters,
32
+ "metrics": self.metrics,
33
+ "tags": self.tags,
34
+ }
35
+
36
+
37
+ class Run:
38
+ """Represents a single pipeline execution run.
39
+
40
+ Example:
41
+ >>> run = Run(run_id="training_20240115_120000", pipeline_name="training_pipeline")
42
+ >>> run.log_metric("accuracy", 0.95)
43
+ >>> run.complete(status="success")
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ run_id: str,
49
+ pipeline_name: str,
50
+ parameters: dict[str, Any] | None = None,
51
+ tags: dict[str, str] | None = None,
52
+ runs_dir: str = ".flowyml/runs",
53
+ ):
54
+ self.run_id = run_id
55
+ self.pipeline_name = pipeline_name
56
+
57
+ self.metadata = RunMetadata(
58
+ run_id=run_id,
59
+ pipeline_name=pipeline_name,
60
+ started_at=datetime.now(),
61
+ parameters=parameters or {},
62
+ tags=tags or {},
63
+ )
64
+
65
+ # Storage
66
+ self.runs_dir = Path(runs_dir)
67
+ self.runs_dir.mkdir(parents=True, exist_ok=True)
68
+
69
+ self._save()
70
+
71
+ def log_metric(self, name: str, value: Any) -> None:
72
+ """Log a metric for this run."""
73
+ self.metadata.metrics[name] = value
74
+ self._save()
75
+
76
+ def log_metrics(self, metrics: dict[str, Any]) -> None:
77
+ """Log multiple metrics."""
78
+ self.metadata.metrics.update(metrics)
79
+ self._save()
80
+
81
+ def log_parameter(self, name: str, value: Any) -> None:
82
+ """Log a parameter."""
83
+ self.metadata.parameters[name] = value
84
+ self._save()
85
+
86
+ def add_tag(self, key: str, value: str) -> None:
87
+ """Add a tag to the run."""
88
+ self.metadata.tags[key] = value
89
+ self._save()
90
+
91
+ def complete(self, status: str = "success") -> None:
92
+ """Mark run as complete.
93
+
94
+ Args:
95
+ status: Final status ("success" or "failed")
96
+ """
97
+ self.metadata.status = status
98
+ self.metadata.ended_at = datetime.now()
99
+ self._save()
100
+
101
+ def get_duration(self) -> float | None:
102
+ """Get run duration in seconds."""
103
+ if self.metadata.ended_at:
104
+ return (self.metadata.ended_at - self.metadata.started_at).total_seconds()
105
+ return None
106
+
107
+ def _save(self) -> None:
108
+ """Save run metadata to disk."""
109
+ run_file = self.runs_dir / f"{self.run_id}.json"
110
+
111
+ with open(run_file, "w") as f:
112
+ json.dump(self.metadata.to_dict(), f, indent=2)
113
+
114
+ @classmethod
115
+ def load(cls, run_id: str, runs_dir: str = ".flowyml/runs") -> Optional["Run"]:
116
+ """Load a run from disk."""
117
+ run_file = Path(runs_dir) / f"{run_id}.json"
118
+
119
+ if not run_file.exists():
120
+ return None
121
+
122
+ try:
123
+ with open(run_file) as f:
124
+ data = json.load(f)
125
+
126
+ run = cls(
127
+ run_id=data["run_id"],
128
+ pipeline_name=data["pipeline_name"],
129
+ parameters=data.get("parameters", {}),
130
+ tags=data.get("tags", {}),
131
+ runs_dir=runs_dir,
132
+ )
133
+
134
+ run.metadata.status = data["status"]
135
+ run.metadata.metrics = data.get("metrics", {})
136
+ if data.get("ended_at"):
137
+ run.metadata.ended_at = datetime.fromisoformat(data["ended_at"])
138
+
139
+ return run
140
+
141
+ except Exception:
142
+ return None
143
+
144
+ def __repr__(self) -> str:
145
+ return f"Run(id='{self.run_id}', status='{self.metadata.status}')"
flowyml/ui/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """flowyml UI module - Real-time UI for pipeline monitoring."""
2
+
3
+ from flowyml.ui.utils import (
4
+ is_ui_running,
5
+ get_ui_url,
6
+ get_run_url,
7
+ get_pipeline_url,
8
+ )
9
+
10
+ __all__ = [
11
+ "is_ui_running",
12
+ "get_ui_url",
13
+ "get_run_url",
14
+ "get_pipeline_url",
15
+ ]
@@ -0,0 +1,31 @@
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ curl \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Install poetry
12
+ RUN curl -sSL https://install.python-poetry.org | python3 -
13
+ ENV PATH="/root/.local/bin:$PATH"
14
+
15
+ # Copy project files
16
+ COPY pyproject.toml poetry.lock ./
17
+ COPY README.md ./
18
+ COPY flowyml ./flowyml
19
+
20
+ # Install dependencies
21
+ RUN poetry config virtualenvs.create false \
22
+ && poetry install --no-interaction --no-ansi --no-root
23
+
24
+ # Install the package itself
25
+ RUN pip install .
26
+
27
+ # Expose port
28
+ EXPOSE 8000
29
+
30
+ # Run the application
31
+ CMD ["uvicorn", "flowyml.ui.backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
File without changes