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
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
+ }