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.
- flowyml/__init__.py +207 -0
- flowyml/assets/__init__.py +22 -0
- flowyml/assets/artifact.py +40 -0
- flowyml/assets/base.py +209 -0
- flowyml/assets/dataset.py +100 -0
- flowyml/assets/featureset.py +301 -0
- flowyml/assets/metrics.py +104 -0
- flowyml/assets/model.py +82 -0
- flowyml/assets/registry.py +157 -0
- flowyml/assets/report.py +315 -0
- flowyml/cli/__init__.py +5 -0
- flowyml/cli/experiment.py +232 -0
- flowyml/cli/init.py +256 -0
- flowyml/cli/main.py +327 -0
- flowyml/cli/run.py +75 -0
- flowyml/cli/stack_cli.py +532 -0
- flowyml/cli/ui.py +33 -0
- flowyml/core/__init__.py +68 -0
- flowyml/core/advanced_cache.py +274 -0
- flowyml/core/approval.py +64 -0
- flowyml/core/cache.py +203 -0
- flowyml/core/checkpoint.py +148 -0
- flowyml/core/conditional.py +373 -0
- flowyml/core/context.py +155 -0
- flowyml/core/error_handling.py +419 -0
- flowyml/core/executor.py +354 -0
- flowyml/core/graph.py +185 -0
- flowyml/core/parallel.py +452 -0
- flowyml/core/pipeline.py +764 -0
- flowyml/core/project.py +253 -0
- flowyml/core/resources.py +424 -0
- flowyml/core/scheduler.py +630 -0
- flowyml/core/scheduler_config.py +32 -0
- flowyml/core/step.py +201 -0
- flowyml/core/step_grouping.py +292 -0
- flowyml/core/templates.py +226 -0
- flowyml/core/versioning.py +217 -0
- flowyml/integrations/__init__.py +1 -0
- flowyml/integrations/keras.py +134 -0
- flowyml/monitoring/__init__.py +1 -0
- flowyml/monitoring/alerts.py +57 -0
- flowyml/monitoring/data.py +102 -0
- flowyml/monitoring/llm.py +160 -0
- flowyml/monitoring/monitor.py +57 -0
- flowyml/monitoring/notifications.py +246 -0
- flowyml/registry/__init__.py +5 -0
- flowyml/registry/model_registry.py +491 -0
- flowyml/registry/pipeline_registry.py +55 -0
- flowyml/stacks/__init__.py +27 -0
- flowyml/stacks/base.py +77 -0
- flowyml/stacks/bridge.py +288 -0
- flowyml/stacks/components.py +155 -0
- flowyml/stacks/gcp.py +499 -0
- flowyml/stacks/local.py +112 -0
- flowyml/stacks/migration.py +97 -0
- flowyml/stacks/plugin_config.py +78 -0
- flowyml/stacks/plugins.py +401 -0
- flowyml/stacks/registry.py +226 -0
- flowyml/storage/__init__.py +26 -0
- flowyml/storage/artifacts.py +246 -0
- flowyml/storage/materializers/__init__.py +20 -0
- flowyml/storage/materializers/base.py +133 -0
- flowyml/storage/materializers/keras.py +185 -0
- flowyml/storage/materializers/numpy.py +94 -0
- flowyml/storage/materializers/pandas.py +142 -0
- flowyml/storage/materializers/pytorch.py +135 -0
- flowyml/storage/materializers/sklearn.py +110 -0
- flowyml/storage/materializers/tensorflow.py +152 -0
- flowyml/storage/metadata.py +931 -0
- flowyml/tracking/__init__.py +1 -0
- flowyml/tracking/experiment.py +211 -0
- flowyml/tracking/leaderboard.py +191 -0
- flowyml/tracking/runs.py +145 -0
- flowyml/ui/__init__.py +15 -0
- flowyml/ui/backend/Dockerfile +31 -0
- flowyml/ui/backend/__init__.py +0 -0
- flowyml/ui/backend/auth.py +163 -0
- flowyml/ui/backend/main.py +187 -0
- flowyml/ui/backend/routers/__init__.py +0 -0
- flowyml/ui/backend/routers/assets.py +45 -0
- flowyml/ui/backend/routers/execution.py +179 -0
- flowyml/ui/backend/routers/experiments.py +49 -0
- flowyml/ui/backend/routers/leaderboard.py +118 -0
- flowyml/ui/backend/routers/notifications.py +72 -0
- flowyml/ui/backend/routers/pipelines.py +110 -0
- flowyml/ui/backend/routers/plugins.py +192 -0
- flowyml/ui/backend/routers/projects.py +85 -0
- flowyml/ui/backend/routers/runs.py +66 -0
- flowyml/ui/backend/routers/schedules.py +222 -0
- flowyml/ui/backend/routers/traces.py +84 -0
- flowyml/ui/frontend/Dockerfile +20 -0
- flowyml/ui/frontend/README.md +315 -0
- flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
- flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
- flowyml/ui/frontend/dist/index.html +16 -0
- flowyml/ui/frontend/index.html +15 -0
- flowyml/ui/frontend/nginx.conf +26 -0
- flowyml/ui/frontend/package-lock.json +3545 -0
- flowyml/ui/frontend/package.json +33 -0
- flowyml/ui/frontend/postcss.config.js +6 -0
- flowyml/ui/frontend/src/App.jsx +21 -0
- flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
- flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
- flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
- flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
- flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
- flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
- flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
- flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
- flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
- flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
- flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
- flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
- flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
- flowyml/ui/frontend/src/components/Layout.jsx +108 -0
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
- flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
- flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
- flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
- flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
- flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
- flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
- flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
- flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
- flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
- flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
- flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
- flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
- flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
- flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
- flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
- flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
- flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
- flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
- flowyml/ui/frontend/src/index.css +11 -0
- flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
- flowyml/ui/frontend/src/main.jsx +10 -0
- flowyml/ui/frontend/src/router/index.jsx +39 -0
- flowyml/ui/frontend/src/services/pluginService.js +90 -0
- flowyml/ui/frontend/src/utils/api.js +47 -0
- flowyml/ui/frontend/src/utils/cn.js +6 -0
- flowyml/ui/frontend/tailwind.config.js +31 -0
- flowyml/ui/frontend/vite.config.js +21 -0
- flowyml/ui/utils.py +77 -0
- flowyml/utils/__init__.py +67 -0
- flowyml/utils/config.py +308 -0
- flowyml/utils/debug.py +240 -0
- flowyml/utils/environment.py +346 -0
- flowyml/utils/git.py +319 -0
- flowyml/utils/logging.py +61 -0
- flowyml/utils/performance.py +314 -0
- flowyml/utils/stack_config.py +296 -0
- flowyml/utils/validation.py +270 -0
- flowyml-1.1.0.dist-info/METADATA +372 -0
- flowyml-1.1.0.dist-info/RECORD +159 -0
- flowyml-1.1.0.dist-info/WHEEL +4 -0
- flowyml-1.1.0.dist-info/entry_points.txt +3 -0
- flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
flowyml/cli/init.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Project initialization CLI commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
TEMPLATES = {
|
|
7
|
+
"basic": {
|
|
8
|
+
"description": "Basic flowyml project structure",
|
|
9
|
+
"files": {
|
|
10
|
+
"pipelines/__init__.py": "",
|
|
11
|
+
"pipelines/training_pipeline.py": """\"\"\"Example training pipeline.\"\"\"
|
|
12
|
+
|
|
13
|
+
from flowyml import Pipeline, step, context
|
|
14
|
+
|
|
15
|
+
# Define context
|
|
16
|
+
ctx = context(
|
|
17
|
+
learning_rate=0.001,
|
|
18
|
+
batch_size=32,
|
|
19
|
+
epochs=10
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@step(outputs=["data/processed"])
|
|
24
|
+
def load_data():
|
|
25
|
+
\"\"\"Load and preprocess data.\"\"\"
|
|
26
|
+
# Your data loading logic here
|
|
27
|
+
print("Loading data...")
|
|
28
|
+
data = {"samples": 1000, "features": 10}
|
|
29
|
+
return data
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@step(inputs=["data/processed"], outputs=["model/trained"])
|
|
33
|
+
def train_model(data, learning_rate, epochs):
|
|
34
|
+
\"\"\"Train the model.\"\"\"
|
|
35
|
+
print(f"Training model with lr={learning_rate}, epochs={epochs}")
|
|
36
|
+
# Your training logic here
|
|
37
|
+
model = {"accuracy": 0.95, "loss": 0.05}
|
|
38
|
+
return model
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@step(inputs=["model/trained"], outputs=["metrics/evaluation"])
|
|
42
|
+
def evaluate_model(model):
|
|
43
|
+
\"\"\"Evaluate the trained model.\"\"\"
|
|
44
|
+
print("Evaluating model...")
|
|
45
|
+
metrics = {"test_accuracy": 0.93, "test_loss": 0.07}
|
|
46
|
+
return metrics
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Create pipeline
|
|
50
|
+
def create_pipeline():
|
|
51
|
+
pipeline = Pipeline("training_pipeline", context=ctx)
|
|
52
|
+
pipeline.add_step(load_data)
|
|
53
|
+
pipeline.add_step(train_model)
|
|
54
|
+
pipeline.add_step(evaluate_model)
|
|
55
|
+
return pipeline
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
pipeline = create_pipeline()
|
|
60
|
+
result = pipeline.run()
|
|
61
|
+
print(f"\\nPipeline completed successfully!")
|
|
62
|
+
print(f"Results: {result.outputs}")
|
|
63
|
+
""",
|
|
64
|
+
"README.md": """# {project_name}
|
|
65
|
+
|
|
66
|
+
A flowyml ML pipeline project.
|
|
67
|
+
|
|
68
|
+
## Getting Started
|
|
69
|
+
|
|
70
|
+
1. Install dependencies:
|
|
71
|
+
```bash
|
|
72
|
+
pip install flowyml
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Run the pipeline:
|
|
76
|
+
```bash
|
|
77
|
+
flowyml run training_pipeline
|
|
78
|
+
# or
|
|
79
|
+
python pipelines/training_pipeline.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
3. View results:
|
|
83
|
+
```bash
|
|
84
|
+
flowyml ui start
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Project Structure
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
{project_name}/
|
|
91
|
+
├── pipelines/ # Pipeline definitions
|
|
92
|
+
│ └── training_pipeline.py
|
|
93
|
+
├── flowyml.yaml # Project configuration
|
|
94
|
+
└── README.md
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Learn More
|
|
98
|
+
|
|
99
|
+
- [flowyml Documentation](https://docs.flowyml.ai)
|
|
100
|
+
- [Examples](https://github.com/flowyml/flowyml/examples)
|
|
101
|
+
""",
|
|
102
|
+
"flowyml.yaml": """# flowyml project configuration
|
|
103
|
+
|
|
104
|
+
name: {project_name}
|
|
105
|
+
version: "0.1.0"
|
|
106
|
+
|
|
107
|
+
# Stack configuration
|
|
108
|
+
stack:
|
|
109
|
+
default: local
|
|
110
|
+
|
|
111
|
+
# Execution settings
|
|
112
|
+
execution:
|
|
113
|
+
max_parallel_steps: 4
|
|
114
|
+
enable_caching: true
|
|
115
|
+
|
|
116
|
+
# Experiment tracking
|
|
117
|
+
tracking:
|
|
118
|
+
auto_log_params: true
|
|
119
|
+
auto_log_metrics: true
|
|
120
|
+
auto_log_artifacts: true
|
|
121
|
+
""",
|
|
122
|
+
".gitignore": """.flowyml/
|
|
123
|
+
*.pyc
|
|
124
|
+
__pycache__/
|
|
125
|
+
.ipynb_checkpoints/
|
|
126
|
+
*.egg-info/
|
|
127
|
+
dist/
|
|
128
|
+
build/
|
|
129
|
+
.DS_Store
|
|
130
|
+
""",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
"pytorch": {
|
|
134
|
+
"description": "PyTorch ML project template",
|
|
135
|
+
"files": {
|
|
136
|
+
"pipelines/__init__.py": "",
|
|
137
|
+
"pipelines/training_pipeline.py": """\"\"\"PyTorch training pipeline.\"\"\"
|
|
138
|
+
|
|
139
|
+
from flowyml import Pipeline, step, context
|
|
140
|
+
import torch
|
|
141
|
+
import torch.nn as nn
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
ctx = context(
|
|
145
|
+
learning_rate=0.001,
|
|
146
|
+
batch_size=64,
|
|
147
|
+
epochs=10,
|
|
148
|
+
device="cuda" if torch.cuda.is_available() else "cpu"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class SimpleModel(nn.Module):
|
|
153
|
+
def __init__(self, input_dim, hidden_dim, output_dim):
|
|
154
|
+
super().__init__()
|
|
155
|
+
self.network = nn.Sequential(
|
|
156
|
+
nn.Linear(input_dim, hidden_dim),
|
|
157
|
+
nn.ReLU(),
|
|
158
|
+
nn.Linear(hidden_dim, output_dim)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def forward(self, x):
|
|
162
|
+
return self.network(x)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@step(outputs=["data/train", "data/val"])
|
|
166
|
+
def prepare_data():
|
|
167
|
+
\"\"\"Prepare training and validation datasets.\"\"\"
|
|
168
|
+
# Your data preparation logic here
|
|
169
|
+
print("Preparing PyTorch datasets...")
|
|
170
|
+
return {"train_size": 8000}, {"val_size": 2000}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@step(inputs=["data/train", "data/val"], outputs=["model/trained"])
|
|
174
|
+
def train_model(train_data, val_data, learning_rate, epochs, device):
|
|
175
|
+
\"\"\"Train PyTorch model.\"\"\"
|
|
176
|
+
print(f"Training on {device}...")
|
|
177
|
+
|
|
178
|
+
model = SimpleModel(input_dim=10, hidden_dim=64, output_dim=2)
|
|
179
|
+
model = model.to(device)
|
|
180
|
+
|
|
181
|
+
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
|
|
182
|
+
|
|
183
|
+
for epoch in range(epochs):
|
|
184
|
+
# Training loop here
|
|
185
|
+
print(f"Epoch {epoch+1}/{epochs}")
|
|
186
|
+
|
|
187
|
+
return model
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def create_pipeline():
|
|
191
|
+
pipeline = Pipeline("pytorch_training", context=ctx)
|
|
192
|
+
pipeline.add_step(prepare_data)
|
|
193
|
+
pipeline.add_step(train_model)
|
|
194
|
+
return pipeline
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
pipeline = create_pipeline()
|
|
199
|
+
result = pipeline.run()
|
|
200
|
+
print("\\nTraining complete!")
|
|
201
|
+
""",
|
|
202
|
+
"requirements.txt": """flowyml
|
|
203
|
+
torch>=2.0.0
|
|
204
|
+
numpy
|
|
205
|
+
""",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def init_project(name: str, template: str, directory: Path) -> None:
|
|
212
|
+
"""Initialize a new flowyml project.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
name: Project name
|
|
216
|
+
template: Template to use (basic, pytorch, tensorflow, sklearn)
|
|
217
|
+
directory: Directory to create project in
|
|
218
|
+
"""
|
|
219
|
+
if template not in TEMPLATES:
|
|
220
|
+
raise ValueError(f"Unknown template: {template}. Choose from: {', '.join(TEMPLATES.keys())}")
|
|
221
|
+
|
|
222
|
+
# Create project directory
|
|
223
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
224
|
+
|
|
225
|
+
# Get template files
|
|
226
|
+
template_data = TEMPLATES[template]
|
|
227
|
+
files = template_data["files"]
|
|
228
|
+
|
|
229
|
+
# Create files from template
|
|
230
|
+
for file_path, content in files.items():
|
|
231
|
+
full_path = directory / file_path
|
|
232
|
+
|
|
233
|
+
# Create parent directories
|
|
234
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
|
|
236
|
+
# Format content with project name
|
|
237
|
+
formatted_content = content.replace("{project_name}", name)
|
|
238
|
+
|
|
239
|
+
# Write file
|
|
240
|
+
with open(full_path, "w") as f:
|
|
241
|
+
f.write(formatted_content)
|
|
242
|
+
|
|
243
|
+
# Create .flowyml directory
|
|
244
|
+
(directory / ".flowyml").mkdir(exist_ok=True)
|
|
245
|
+
(directory / ".flowyml" / "artifacts").mkdir(exist_ok=True)
|
|
246
|
+
(directory / ".flowyml" / "cache").mkdir(exist_ok=True)
|
|
247
|
+
(directory / ".flowyml" / "runs").mkdir(exist_ok=True)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def list_templates() -> dict:
|
|
251
|
+
"""List available project templates.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Dictionary of template names and descriptions
|
|
255
|
+
"""
|
|
256
|
+
return {name: data["description"] for name, data in TEMPLATES.items()}
|
flowyml/cli/main.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Main CLI entry point for flowyml."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from flowyml.utils.config import get_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
@click.version_option(version="0.1.0", prog_name="flowyml")
|
|
10
|
+
def cli() -> None:
|
|
11
|
+
"""Flowyml - Next-Generation ML Pipeline Framework.
|
|
12
|
+
|
|
13
|
+
A developer-first ML pipeline orchestration framework that makes
|
|
14
|
+
ML pipelines feel effortless while providing production-grade capabilities.
|
|
15
|
+
"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@cli.command()
|
|
20
|
+
@click.option("--name", prompt="Project name", help="Name of the project")
|
|
21
|
+
@click.option(
|
|
22
|
+
"--template",
|
|
23
|
+
default="basic",
|
|
24
|
+
type=click.Choice(["basic", "pytorch", "tensorflow", "sklearn"]),
|
|
25
|
+
help="Project template",
|
|
26
|
+
)
|
|
27
|
+
@click.option("--dir", "directory", default=".", help="Directory to create project in")
|
|
28
|
+
def init(name: str, template: str, directory: str) -> None:
|
|
29
|
+
"""Initialize a new flowyml project."""
|
|
30
|
+
from flowyml.cli.init import init_project
|
|
31
|
+
|
|
32
|
+
project_dir = Path(directory) / name
|
|
33
|
+
click.echo(f"Initializing flowyml project '{name}' with template '{template}'...")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
init_project(name, template, project_dir)
|
|
37
|
+
click.echo(f"✓ Project '{name}' created successfully at {project_dir}")
|
|
38
|
+
click.echo("\nNext steps:")
|
|
39
|
+
click.echo(f" cd {name}")
|
|
40
|
+
click.echo(" flowyml run training_pipeline")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
click.echo(f"✗ Error creating project: {e}", err=True)
|
|
43
|
+
raise click.Abort()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@cli.command()
|
|
47
|
+
@click.argument("pipeline_name")
|
|
48
|
+
@click.option("--stack", default="local", help="Stack to use for execution")
|
|
49
|
+
@click.option("--context", "-c", multiple=True, help="Context parameters (key=value)")
|
|
50
|
+
@click.option("--debug", is_flag=True, help="Enable debug mode")
|
|
51
|
+
def run(pipeline_name: str, stack: str, context: tuple, debug: bool) -> None:
|
|
52
|
+
"""Run a pipeline."""
|
|
53
|
+
from flowyml.cli.run import run_pipeline
|
|
54
|
+
|
|
55
|
+
# Parse context parameters
|
|
56
|
+
ctx_params = {}
|
|
57
|
+
for param in context:
|
|
58
|
+
key, value = param.split("=", 1)
|
|
59
|
+
ctx_params[key] = value
|
|
60
|
+
|
|
61
|
+
click.echo(f"Running pipeline '{pipeline_name}' on stack '{stack}'...")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
result = run_pipeline(pipeline_name, stack, ctx_params, debug)
|
|
65
|
+
click.echo("✓ Pipeline completed successfully")
|
|
66
|
+
click.echo(f" Run ID: {result.get('run_id', 'N/A')}")
|
|
67
|
+
click.echo(f" Duration: {result.get('duration', 'N/A')}")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
click.echo(f"✗ Pipeline failed: {e}", err=True)
|
|
70
|
+
raise click.Abort()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@cli.group()
|
|
74
|
+
def ui() -> None:
|
|
75
|
+
"""UI server commands."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@ui.command()
|
|
80
|
+
@click.option("--host", default="localhost", help="Host to bind to")
|
|
81
|
+
@click.option("--port", default=8080, help="Port to bind to")
|
|
82
|
+
@click.option("--dev", is_flag=True, help="Run in development mode")
|
|
83
|
+
@click.option("--open-browser", "-o", is_flag=True, help="Open browser automatically")
|
|
84
|
+
def start(host: str, port: int, dev: bool, open_browser: bool) -> None:
|
|
85
|
+
"""Start the flowyml UI server."""
|
|
86
|
+
from flowyml.ui.utils import is_ui_running
|
|
87
|
+
|
|
88
|
+
# Check if already running
|
|
89
|
+
if is_ui_running(host, port):
|
|
90
|
+
click.echo(f"ℹ️ UI server is already running at http://{host}:{port}")
|
|
91
|
+
if open_browser:
|
|
92
|
+
import webbrowser
|
|
93
|
+
|
|
94
|
+
webbrowser.open(f"http://{host}:{port}")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
click.echo(f"🚀 Starting flowyml UI on http://{host}:{port}...")
|
|
98
|
+
if dev:
|
|
99
|
+
click.echo(" Development mode: Auto-reload enabled")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
from flowyml.cli.ui import start_ui_server
|
|
103
|
+
|
|
104
|
+
# Open browser if requested
|
|
105
|
+
if open_browser:
|
|
106
|
+
import webbrowser
|
|
107
|
+
import threading
|
|
108
|
+
|
|
109
|
+
def open_browser_delayed() -> None:
|
|
110
|
+
import time
|
|
111
|
+
|
|
112
|
+
time.sleep(1.5) # Wait for server to start
|
|
113
|
+
webbrowser.open(f"http://{host}:{port}")
|
|
114
|
+
|
|
115
|
+
threading.Thread(target=open_browser_delayed, daemon=True).start()
|
|
116
|
+
|
|
117
|
+
start_ui_server(host, port, dev)
|
|
118
|
+
except ImportError:
|
|
119
|
+
click.echo("✗ UI server not available. Install with: pip install flowyml[ui]", err=True)
|
|
120
|
+
raise click.Abort()
|
|
121
|
+
except Exception as e:
|
|
122
|
+
click.echo(f"✗ Error starting UI: {e}", err=True)
|
|
123
|
+
raise click.Abort()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@ui.command()
|
|
127
|
+
def stop() -> None:
|
|
128
|
+
"""Stop the flowyml UI server."""
|
|
129
|
+
click.echo("Stopping flowyml UI server...")
|
|
130
|
+
click.echo("ℹ️ To stop the UI server:")
|
|
131
|
+
click.echo(" - If running in foreground: Press Ctrl+C")
|
|
132
|
+
click.echo(" - If running in background: pkill -f 'flowyml ui start'")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@ui.command()
|
|
136
|
+
@click.option("--host", default="localhost", help="Host to check")
|
|
137
|
+
@click.option("--port", default=8080, help="Port to check")
|
|
138
|
+
def status(host: str, port: int) -> None:
|
|
139
|
+
"""Check if the UI server is running."""
|
|
140
|
+
from flowyml.ui.utils import is_ui_running, get_ui_url
|
|
141
|
+
|
|
142
|
+
if is_ui_running(host, port):
|
|
143
|
+
url = get_ui_url(host, port)
|
|
144
|
+
click.echo(f"✅ UI server is running at {url}")
|
|
145
|
+
click.echo(" Status: Healthy")
|
|
146
|
+
click.echo(f" Health endpoint: {url}/api/health")
|
|
147
|
+
else:
|
|
148
|
+
click.echo(f"❌ UI server is not running on {host}:{port}")
|
|
149
|
+
click.echo(f" Start with: flowyml ui start --host {host} --port {port}")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@cli.group()
|
|
153
|
+
def experiment() -> None:
|
|
154
|
+
"""Experiment tracking commands."""
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@experiment.command("list")
|
|
159
|
+
@click.option("--limit", default=10, help="Number of experiments to show")
|
|
160
|
+
@click.option("--pipeline", help="Filter by pipeline name")
|
|
161
|
+
def list_experiments(limit: int, pipeline: str) -> None:
|
|
162
|
+
"""List experiments."""
|
|
163
|
+
from flowyml.cli.experiment import list_experiments_cmd
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
experiments = list_experiments_cmd(limit, pipeline)
|
|
167
|
+
click.echo(f"Found {len(experiments)} experiments:\n")
|
|
168
|
+
|
|
169
|
+
for exp in experiments:
|
|
170
|
+
click.echo(f" {exp['name']}")
|
|
171
|
+
click.echo(f" Runs: {exp.get('num_runs', 0)}")
|
|
172
|
+
click.echo(f" Created: {exp.get('created_at', 'N/A')}")
|
|
173
|
+
click.echo()
|
|
174
|
+
except Exception as e:
|
|
175
|
+
click.echo(f"✗ Error listing experiments: {e}", err=True)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@experiment.command()
|
|
179
|
+
@click.argument("run_ids", nargs=-1, required=True)
|
|
180
|
+
def compare(run_ids: tuple) -> None:
|
|
181
|
+
"""Compare multiple experiment runs."""
|
|
182
|
+
from flowyml.cli.experiment import compare_runs
|
|
183
|
+
|
|
184
|
+
click.echo(f"Comparing {len(run_ids)} runs...")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
comparison = compare_runs(list(run_ids))
|
|
188
|
+
click.echo("\nComparison Results:")
|
|
189
|
+
click.echo(comparison)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
click.echo(f"✗ Error comparing runs: {e}", err=True)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@cli.group()
|
|
195
|
+
def stack() -> None:
|
|
196
|
+
"""Stack management commands."""
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@stack.command("list")
|
|
201
|
+
def list_stacks() -> None:
|
|
202
|
+
"""List available stacks."""
|
|
203
|
+
click.echo("Available stacks:\n")
|
|
204
|
+
click.echo(" local (default) - Local execution")
|
|
205
|
+
click.echo(" aws - AWS (SageMaker, S3, Step Functions)")
|
|
206
|
+
click.echo(" gcp - Google Cloud (Vertex AI, GCS)")
|
|
207
|
+
click.echo(" azure - Azure (ML, Blob Storage)")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@stack.command()
|
|
211
|
+
@click.argument("stack_name")
|
|
212
|
+
def switch(stack_name: str) -> None:
|
|
213
|
+
"""Switch active stack."""
|
|
214
|
+
config = get_config()
|
|
215
|
+
config.default_stack = stack_name
|
|
216
|
+
config.save()
|
|
217
|
+
click.echo(f"✓ Switched to stack '{stack_name}'")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@cli.group()
|
|
221
|
+
def cache() -> None:
|
|
222
|
+
"""Cache management commands."""
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@cache.command()
|
|
227
|
+
def stats() -> None:
|
|
228
|
+
"""Show cache statistics."""
|
|
229
|
+
from flowyml.core.cache import CacheStore
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
cache = CacheStore()
|
|
233
|
+
stats = cache.get_stats()
|
|
234
|
+
|
|
235
|
+
click.echo("Cache Statistics:\n")
|
|
236
|
+
click.echo(f" Hits: {stats['hits']}")
|
|
237
|
+
click.echo(f" Misses: {stats['misses']}")
|
|
238
|
+
click.echo(f" Hit Rate: {stats.get('hit_rate', 0):.1%}")
|
|
239
|
+
click.echo(f" Total Entries: {stats.get('total_entries', 0)}")
|
|
240
|
+
click.echo(f" Size: {stats.get('size_mb', 0):.2f} MB")
|
|
241
|
+
except Exception as e:
|
|
242
|
+
click.echo(f"✗ Error getting cache stats: {e}", err=True)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@cache.command()
|
|
246
|
+
@click.confirmation_option(prompt="Are you sure you want to clear the cache?")
|
|
247
|
+
def clear() -> None:
|
|
248
|
+
"""Clear all cache."""
|
|
249
|
+
from flowyml.core.cache import CacheStore
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
cache = CacheStore()
|
|
253
|
+
cache.clear()
|
|
254
|
+
click.echo("✓ Cache cleared successfully")
|
|
255
|
+
except Exception as e:
|
|
256
|
+
click.echo(f"✗ Error clearing cache: {e}", err=True)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@cli.group()
|
|
260
|
+
def config() -> None:
|
|
261
|
+
"""Configuration management commands."""
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@config.command("show")
|
|
266
|
+
def show_config() -> None:
|
|
267
|
+
"""Show current configuration."""
|
|
268
|
+
cfg = get_config()
|
|
269
|
+
|
|
270
|
+
click.echo("flowyml Configuration:\n")
|
|
271
|
+
click.echo(f" flowyml Home: {cfg.flowyml_home}")
|
|
272
|
+
click.echo(f" Artifacts Dir: {cfg.artifacts_dir}")
|
|
273
|
+
click.echo(f" Metadata DB: {cfg.metadata_db}")
|
|
274
|
+
click.echo(f" Default Stack: {cfg.default_stack}")
|
|
275
|
+
click.echo(f" Execution Mode: {cfg.execution_mode}")
|
|
276
|
+
if cfg.execution_mode == "remote":
|
|
277
|
+
click.echo(f" Remote Server URL: {cfg.remote_server_url}")
|
|
278
|
+
click.echo(f" Remote UI URL: {cfg.remote_ui_url}")
|
|
279
|
+
click.echo(f" Enable Caching: {cfg.enable_caching}")
|
|
280
|
+
click.echo(f" Log Level: {cfg.log_level}")
|
|
281
|
+
click.echo(f" UI Port: {cfg.ui_port}")
|
|
282
|
+
click.echo(f" Debug Mode: {cfg.debug_mode}")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@config.command("set-mode")
|
|
286
|
+
@click.argument("mode", type=click.Choice(["local", "remote"]))
|
|
287
|
+
def set_mode(mode: str) -> None:
|
|
288
|
+
"""Set execution mode (local or remote)."""
|
|
289
|
+
cfg = get_config()
|
|
290
|
+
cfg.execution_mode = mode
|
|
291
|
+
cfg.save()
|
|
292
|
+
click.echo(f"✓ Execution mode set to '{mode}'")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@config.command("set-url")
|
|
296
|
+
@click.option("--server", help="Remote server URL")
|
|
297
|
+
@click.option("--ui", help="Remote UI URL")
|
|
298
|
+
def set_url(server: str, ui: str) -> None:
|
|
299
|
+
"""Set remote server and UI URLs."""
|
|
300
|
+
cfg = get_config()
|
|
301
|
+
if server:
|
|
302
|
+
cfg.remote_server_url = server
|
|
303
|
+
click.echo(f"✓ Remote server URL set to '{server}'")
|
|
304
|
+
if ui:
|
|
305
|
+
cfg.remote_ui_url = ui
|
|
306
|
+
click.echo(f"✓ Remote UI URL set to '{ui}'")
|
|
307
|
+
cfg.save()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@cli.command()
|
|
311
|
+
@click.argument("run_id")
|
|
312
|
+
@click.option("--step", help="Filter by step name")
|
|
313
|
+
@click.option("--tail", default=100, help="Number of lines to show")
|
|
314
|
+
def logs(run_id: str, step: str, tail: int) -> None:
|
|
315
|
+
"""View logs for a pipeline run."""
|
|
316
|
+
click.echo(f"Logs for run '{run_id}':")
|
|
317
|
+
|
|
318
|
+
if step:
|
|
319
|
+
click.echo(f" Step: {step}")
|
|
320
|
+
|
|
321
|
+
click.echo("\nLog entries:")
|
|
322
|
+
click.echo(f" (Showing last {tail} lines)")
|
|
323
|
+
click.echo(" [Log output would appear here]")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == "__main__":
|
|
327
|
+
cli()
|
flowyml/cli/run.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Pipeline execution CLI commands."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_pipeline(pipeline_name: str, stack: str, context_params: dict[str, Any], debug: bool) -> dict[str, Any]:
|
|
10
|
+
"""Run a pipeline by name.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
pipeline_name: Name of the pipeline to run
|
|
14
|
+
stack: Stack to use for execution
|
|
15
|
+
context_params: Context parameters to override
|
|
16
|
+
debug: Enable debug mode
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Dictionary with run results
|
|
20
|
+
"""
|
|
21
|
+
# Try to find pipeline file
|
|
22
|
+
pipeline_paths = [
|
|
23
|
+
Path(f"pipelines/{pipeline_name}.py"),
|
|
24
|
+
Path(f"{pipeline_name}.py"),
|
|
25
|
+
Path(f"pipelines/{pipeline_name}_pipeline.py"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
pipeline_file = None
|
|
29
|
+
for path in pipeline_paths:
|
|
30
|
+
if path.exists():
|
|
31
|
+
pipeline_file = path
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
if pipeline_file is None:
|
|
35
|
+
raise FileNotFoundError(
|
|
36
|
+
f"Pipeline '{pipeline_name}' not found. Looked in:\n" + "\n".join(f" - {p}" for p in pipeline_paths),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Load pipeline module
|
|
40
|
+
spec = importlib.util.spec_from_file_location(pipeline_name, pipeline_file)
|
|
41
|
+
if spec is None or spec.loader is None:
|
|
42
|
+
raise ImportError(f"Could not load pipeline from {pipeline_file}")
|
|
43
|
+
|
|
44
|
+
module = importlib.util.module_from_spec(spec)
|
|
45
|
+
sys.modules[pipeline_name] = module
|
|
46
|
+
spec.loader.exec_module(module)
|
|
47
|
+
|
|
48
|
+
# Get pipeline
|
|
49
|
+
if hasattr(module, "create_pipeline"):
|
|
50
|
+
pipeline = module.create_pipeline()
|
|
51
|
+
elif hasattr(module, "pipeline"):
|
|
52
|
+
pipeline = module.pipeline
|
|
53
|
+
else:
|
|
54
|
+
raise AttributeError(
|
|
55
|
+
"Pipeline module must define 'create_pipeline()' function or 'pipeline' variable",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Override context parameters
|
|
59
|
+
if context_params:
|
|
60
|
+
for key, value in context_params.items():
|
|
61
|
+
setattr(pipeline.context, key, value)
|
|
62
|
+
|
|
63
|
+
# Set stack if not default
|
|
64
|
+
if stack != "local":
|
|
65
|
+
pipeline.set_stack(stack)
|
|
66
|
+
|
|
67
|
+
# Run pipeline
|
|
68
|
+
result = pipeline.run(debug=debug)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"run_id": result.run_id,
|
|
72
|
+
"status": result.status,
|
|
73
|
+
"duration": result.duration,
|
|
74
|
+
"outputs": result.outputs,
|
|
75
|
+
}
|