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,532 @@
1
+ """Enhanced CLI for flowyml with stack management.
2
+
3
+ Allows running pipelines with different stacks from command line
4
+ without modifying pipeline code.
5
+ """
6
+
7
+ import click
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ @click.group()
13
+ @click.version_option()
14
+ def cli() -> None:
15
+ """Flowyml - Unified ML Pipeline Framework."""
16
+ pass
17
+
18
+
19
+ @cli.group()
20
+ def component() -> None:
21
+ """Manage stack components and plugins."""
22
+ pass
23
+
24
+
25
+ @component.command("list")
26
+ @click.option("--type", "-t", "component_type", help="Filter by component type")
27
+ def list_components(component_type: str | None) -> None:
28
+ """List all registered components."""
29
+ from flowyml.stacks.plugins import get_component_registry
30
+
31
+ registry = get_component_registry()
32
+ components = registry.list_all()
33
+
34
+ if component_type:
35
+ if component_type in components:
36
+ click.echo(f"\n{component_type.capitalize()}:")
37
+ for name in components[component_type]:
38
+ click.echo(f" • {name}")
39
+ else:
40
+ click.echo(f"Unknown component type: {component_type}", err=True)
41
+ return
42
+
43
+ click.echo("\n📦 Registered Components:")
44
+ for comp_type, names in components.items():
45
+ if names:
46
+ click.echo(f"\n{comp_type.capitalize()}:")
47
+ for name in names:
48
+ click.echo(f" • {name}")
49
+ click.echo()
50
+
51
+
52
+ @component.command("load")
53
+ @click.argument("source")
54
+ @click.option("--name", "-n", help="Custom name for component")
55
+ def load_component_cli(source: str, name: str | None) -> None:
56
+ """Load a component from various sources.
57
+
58
+ Examples:
59
+ # From module
60
+ flowyml component load my_package.components
61
+
62
+ # From file
63
+ flowyml component load /path/to/component.py:MyOrchestrator
64
+
65
+ # From ZenML
66
+ flowyml component load zenml:zenml.orchestrators.kubernetes.KubernetesOrchestrator
67
+ """
68
+ from flowyml.stacks.plugins import load_component
69
+
70
+ try:
71
+ load_component(source, name)
72
+ click.echo(f"✅ Loaded component from: {source}")
73
+
74
+ # Show what was loaded
75
+ from flowyml.stacks.plugins import get_component_registry
76
+
77
+ registry = get_component_registry()
78
+ components = registry.list_all()
79
+
80
+ click.echo("\nAvailable components:")
81
+ for comp_type, names in components.items():
82
+ for comp_name in names:
83
+ if name and comp_name == name:
84
+ click.echo(f" • {comp_name} [{comp_type}] ⭐ NEW")
85
+
86
+ except Exception as e:
87
+ click.echo(f"❌ Error loading component: {e}", err=True)
88
+ sys.exit(1)
89
+
90
+
91
+ @cli.group()
92
+ def stack() -> None:
93
+ """Manage infrastructure stacks."""
94
+ pass
95
+
96
+
97
+ @stack.command("list")
98
+ @click.option("--config", "-c", help="Path to flowyml.yaml")
99
+ def list_stacks(config: str | None) -> None:
100
+ """List all configured stacks."""
101
+ from flowyml.utils.stack_config import load_config
102
+
103
+ loader = load_config(config)
104
+ stacks = loader.list_stacks()
105
+
106
+ if not stacks:
107
+ click.echo("No stacks configured. Create a flowyml.yaml file.")
108
+ return
109
+
110
+ default = loader.get_default_stack()
111
+
112
+ click.echo("\nConfigured stacks:")
113
+ for stack_name in stacks:
114
+ marker = " (default)" if stack_name == default else ""
115
+ config_data = loader.get_stack_config(stack_name)
116
+ stack_type = config_data.get("type", "unknown")
117
+ click.echo(f" • {stack_name}{marker} [{stack_type}]")
118
+ click.echo()
119
+
120
+
121
+ @stack.command("show")
122
+ @click.argument("stack_name")
123
+ @click.option("--config", "-c", help="Path to flowyml.yaml")
124
+ def show_stack(stack_name: str, config: str | None) -> None:
125
+ """Show detailed stack configuration."""
126
+ from flowyml.utils.stack_config import load_config
127
+ import yaml
128
+
129
+ loader = load_config(config)
130
+ stack_config = loader.get_stack_config(stack_name)
131
+
132
+ if not stack_config:
133
+ click.echo(f"Stack '{stack_name}' not found", err=True)
134
+ sys.exit(1)
135
+
136
+ click.echo(f"\nStack: {stack_name}")
137
+ click.echo(yaml.dump(stack_config, default_flow_style=False))
138
+
139
+
140
+ @stack.command("set-default")
141
+ @click.argument("stack_name")
142
+ @click.option("--config", "-c", help="Path to flowyml.yaml")
143
+ def set_default_stack(stack_name: str, config: str | None) -> None:
144
+ """Set the default stack."""
145
+ from flowyml.stacks.registry import get_registry
146
+
147
+ registry = get_registry()
148
+
149
+ if stack_name not in registry.list_stacks():
150
+ click.echo(f"Stack '{stack_name}' not found", err=True)
151
+ sys.exit(1)
152
+
153
+ registry.set_active_stack(stack_name)
154
+ click.echo(f"Set '{stack_name}' as active stack")
155
+
156
+
157
+ @cli.command()
158
+ @click.argument("pipeline_file")
159
+ @click.option("--stack", "-s", help="Stack to use (from flowyml.yaml)")
160
+ @click.option("--resources", "-r", help="Resource configuration to use")
161
+ @click.option("--config", "-c", help="Path to flowyml.yaml")
162
+ @click.option("--context", "-ctx", multiple=True, help="Context variables (key=value)")
163
+ @click.option("--dry-run", is_flag=True, help="Show what would be executed without running")
164
+ def run(
165
+ pipeline_file: str,
166
+ stack: str | None,
167
+ resources: str | None,
168
+ config: str | None,
169
+ context: tuple,
170
+ dry_run: bool,
171
+ ) -> None:
172
+ """Run a pipeline with specified stack and resources.
173
+
174
+ Examples:
175
+ # Run with local stack
176
+ flowyml run pipeline.py
177
+
178
+ # Run on production stack
179
+ flowyml run pipeline.py --stack production
180
+
181
+ # Run with GPU resources
182
+ flowyml run pipeline.py --stack production --resources gpu_training
183
+
184
+ # Pass context variables
185
+ flowyml run pipeline.py --context data_path=gs://bucket/data.csv
186
+ """
187
+ from flowyml.utils.stack_config import (
188
+ load_config,
189
+ create_stack_from_config,
190
+ create_resource_config_from_dict,
191
+ create_docker_config_from_dict,
192
+ )
193
+ import importlib.util
194
+
195
+ # Load configuration
196
+ loader = load_config(config)
197
+
198
+ # Determine stack to use
199
+ stack_name = stack or loader.get_default_stack() or "local"
200
+
201
+ click.echo(f"🚀 Running pipeline: {pipeline_file}")
202
+ click.echo(f"📦 Stack: {stack_name}")
203
+
204
+ if resources:
205
+ click.echo(f"💻 Resources: {resources}")
206
+
207
+ # Get stack configuration
208
+ stack_config = loader.get_stack_config(stack_name)
209
+ if not stack_config:
210
+ click.echo(f"Stack '{stack_name}' not found in configuration", err=True)
211
+ sys.exit(1)
212
+
213
+ # Create stack instance
214
+ stack_instance = create_stack_from_config(stack_config, stack_name)
215
+
216
+ # Get resource configuration
217
+ resource_config = None
218
+ if resources:
219
+ resource_dict = loader.get_resource_config(resources)
220
+ if resource_dict:
221
+ resource_config = create_resource_config_from_dict(resource_dict)
222
+
223
+ # Get Docker configuration
224
+ docker_dict = loader.get_docker_config()
225
+ docker_config = create_docker_config_from_dict(docker_dict)
226
+
227
+ # Parse context variables
228
+ context_dict = {}
229
+ for ctx_item in context:
230
+ if "=" in ctx_item:
231
+ key, value = ctx_item.split("=", 1)
232
+ context_dict[key] = value
233
+
234
+ if dry_run:
235
+ click.echo("\n🔍 Dry run - configuration:")
236
+ click.echo(f" Stack: {stack_instance}")
237
+ click.echo(f" Resources: {resource_config}")
238
+ click.echo(f" Docker: {docker_config}")
239
+ click.echo(f" Context: {context_dict}")
240
+ return
241
+
242
+ # Load and run pipeline
243
+ click.echo("\n⚙️ Loading pipeline...")
244
+
245
+ # Import the pipeline file
246
+ spec = importlib.util.spec_from_file_location("pipeline_module", pipeline_file)
247
+ if spec is None or spec.loader is None:
248
+ click.echo(f"Could not load pipeline file: {pipeline_file}", err=True)
249
+ sys.exit(1)
250
+
251
+ module = importlib.util.module_from_spec(spec)
252
+ sys.modules["pipeline_module"] = module
253
+ spec.loader.exec_module(module)
254
+
255
+ # Find pipeline instance
256
+ pipeline = None
257
+ for attr_name in dir(module):
258
+ attr = getattr(module, attr_name)
259
+ if hasattr(attr, "__class__") and attr.__class__.__name__ == "Pipeline":
260
+ pipeline = attr
261
+ break
262
+
263
+ if pipeline is None:
264
+ click.echo("No Pipeline instance found in file", err=True)
265
+ sys.exit(1)
266
+
267
+ # Override stack
268
+ pipeline.stack = stack_instance
269
+
270
+ click.echo("🏃 Running pipeline...\n")
271
+
272
+ # Run pipeline
273
+ result = pipeline.run(
274
+ context=context_dict,
275
+ resources=resource_config,
276
+ docker_config=docker_config,
277
+ )
278
+
279
+ click.echo("\n✅ Pipeline completed successfully!")
280
+ click.echo(f"Results: {result}")
281
+
282
+
283
+ @cli.command()
284
+ @click.option("--output", "-o", default="flowyml.yaml", help="Output file path")
285
+ def init(output: str) -> None:
286
+ """Initialize a new flowyml project with example configuration."""
287
+ import shutil
288
+
289
+ # Copy example config
290
+ example_path = Path(__file__).parent.parent.parent / "flowyml.yaml.example"
291
+ output_path = Path(output)
292
+
293
+ if output_path.exists():
294
+ click.confirm(f"{output} already exists. Overwrite?", abort=True)
295
+
296
+ if example_path.exists():
297
+ shutil.copy(example_path, output_path)
298
+ click.echo(f"✅ Created {output}")
299
+ else:
300
+ # Create basic config
301
+ basic_config = """# flowyml Configuration
302
+
303
+ stacks:
304
+ local:
305
+ type: local
306
+ artifact_store:
307
+ path: .flowyml/artifacts
308
+ metadata_store:
309
+ path: .flowyml/metadata.db
310
+
311
+ default_stack: local
312
+
313
+ resources:
314
+ default:
315
+ cpu: "2"
316
+ memory: "8Gi"
317
+
318
+ docker:
319
+ base_image: "python:3.11-slim"
320
+ use_poetry: true
321
+ """
322
+ with open(output_path, "w") as f:
323
+ f.write(basic_config)
324
+
325
+ click.echo(f"✅ Created {output}")
326
+
327
+ click.echo("\nNext steps:")
328
+ click.echo(" 1. Edit flowyml.yaml to configure your stacks")
329
+ click.echo(" 2. Run: flowyml stack list")
330
+ click.echo(" 3. Run your pipeline: flowyml run pipeline.py")
331
+
332
+
333
+ @cli.group()
334
+ def plugin() -> None:
335
+ """Manage plugins and integrations."""
336
+ pass
337
+
338
+
339
+ @plugin.command("list")
340
+ @click.option("--installed", is_flag=True, help="Show only installed plugins")
341
+ def list_plugins(installed: bool) -> None:
342
+ """List available and installed plugins."""
343
+ from flowyml.stacks.plugins import get_component_registry
344
+
345
+ registry = get_component_registry()
346
+ plugins = registry.list_plugins()
347
+
348
+ if not plugins:
349
+ click.echo("No plugins found.")
350
+ return
351
+
352
+ click.echo("\n🔌 Plugins:")
353
+ for p in plugins:
354
+ status = "✅ Installed" if p.is_installed else "Available"
355
+ click.echo(f" • {p.name} ({p.version}) - {status}")
356
+ if p.description:
357
+ click.echo(f" {p.description}")
358
+ click.echo()
359
+
360
+
361
+ @plugin.command("search")
362
+ @click.argument("query", required=False)
363
+ @click.option("--source", "-s", type=click.Choice(["pypi", "zenml", "all"]), default="all")
364
+ def search_plugins(query: str | None, source: str) -> None:
365
+ """Search for available plugins."""
366
+ click.echo(f"Searching for plugins matching '{query or '*'}' from {source}...")
367
+
368
+ # In a real implementation, this would query PyPI or a central registry
369
+ # For now, we'll simulate discovery of common ZenML plugins
370
+
371
+ common_plugins = [
372
+ {"name": "zenml-kubernetes", "desc": "Kubernetes orchestrator for ZenML/flowyml"},
373
+ {"name": "zenml-mlflow", "desc": "MLflow integration for experiment tracking"},
374
+ {"name": "zenml-aws", "desc": "AWS stack components (S3, ECR, SageMaker)"},
375
+ {"name": "zenml-gcp", "desc": "Google Cloud stack components"},
376
+ {"name": "zenml-azure", "desc": "Azure stack components"},
377
+ {"name": "zenml-airflow", "desc": "Airflow orchestrator integration"},
378
+ ]
379
+
380
+ found = False
381
+ for p in common_plugins:
382
+ if not query or query.lower() in p["name"] or query.lower() in p["desc"].lower():
383
+ click.echo(f"\n📦 {p['name']}")
384
+ click.echo(f" {p['desc']}")
385
+ click.echo(f" Install: flowyml plugin install {p['name']}")
386
+ found = True
387
+
388
+ if not found:
389
+ click.echo("No plugins found matching your query.")
390
+
391
+
392
+ @plugin.command("install")
393
+ @click.argument("plugin_name")
394
+ def install_plugin(plugin_name: str) -> None:
395
+ """Install a plugin."""
396
+ from flowyml.stacks.plugins import get_component_registry
397
+
398
+ registry = get_component_registry()
399
+
400
+ try:
401
+ from rich.console import Console
402
+
403
+ console = Console()
404
+
405
+ with console.status(f"[bold green]Installing {plugin_name}..."):
406
+ if registry.install_plugin(plugin_name):
407
+ console.print(f"[bold green]✅ Successfully installed {plugin_name}![/bold green]")
408
+ else:
409
+ console.print(f"[bold red]❌ Failed to install {plugin_name}[/bold red]")
410
+
411
+ except ImportError:
412
+ click.echo(f"Installing {plugin_name}...")
413
+ if registry.install_plugin(plugin_name):
414
+ click.echo(f"✅ Successfully installed {plugin_name}!")
415
+ else:
416
+ click.echo(f"❌ Failed to install {plugin_name}")
417
+
418
+
419
+ @plugin.command("info")
420
+ @click.argument("plugin_name")
421
+ def plugin_info(plugin_name: str) -> None:
422
+ """Get detailed info about a plugin."""
423
+ # Simulated info
424
+ info = {
425
+ "name": plugin_name,
426
+ "version": "1.0.0",
427
+ "author": "flowyml Team",
428
+ "description": "A powerful plugin for flowyml.",
429
+ "components": ["Orchestrator", "ArtifactStore"],
430
+ "dependencies": ["zenml>=0.40.0", "boto3"],
431
+ }
432
+
433
+ try:
434
+ from rich.console import Console
435
+ from rich.markdown import Markdown
436
+ from rich.panel import Panel
437
+
438
+ console = Console()
439
+
440
+ content = f"""
441
+ # {info['name']} (v{info['version']})
442
+
443
+ {info['description']}
444
+
445
+ **Author:** {info['author']}
446
+
447
+ ## Components
448
+ {chr(10).join(f'- {c}' for c in info['components'])}
449
+
450
+ ## Dependencies
451
+ {chr(10).join(f'- {d}' for d in info['dependencies'])}
452
+ """
453
+ console.print(Panel(Markdown(content), title="Plugin Info", expand=False))
454
+
455
+ except ImportError:
456
+ click.echo(f"Plugin: {info['name']}")
457
+ click.echo(f"Version: {info['version']}")
458
+ click.echo(f"Description: {info['description']}")
459
+
460
+
461
+ @plugin.command("import-zenml-stack")
462
+ @click.argument("stack_name")
463
+ @click.option("--output", "-o", default="flowyml.yaml", help="Output file path")
464
+ def import_zenml_stack(stack_name: str, output: str) -> None:
465
+ """Import an existing ZenML stack."""
466
+ from flowyml.stacks.migration import StackMigrator
467
+
468
+ migrator = StackMigrator()
469
+
470
+ try:
471
+ # Try to use rich if available
472
+ try:
473
+ from rich.console import Console
474
+ from rich.panel import Panel
475
+
476
+ console = Console()
477
+ use_rich = True
478
+ except ImportError:
479
+ use_rich = False
480
+
481
+ if use_rich:
482
+ console.print(f"🔍 Analyzing ZenML stack [bold cyan]'{stack_name}'[/bold cyan]...")
483
+ else:
484
+ click.echo(f"🔍 Analyzing ZenML stack '{stack_name}'...")
485
+
486
+ migration_data = migrator.migrate_zenml_stack(stack_name)
487
+
488
+ msg = f"✅ Found stack '{stack_name}' with {len(migration_data['plugins'])} components."
489
+ if use_rich:
490
+ console.print(f"[bold green]{msg}[/bold green]")
491
+ console.print("\n[bold]Plugins to configure:[/bold]")
492
+ for p in migration_data["plugins"]:
493
+ console.print(f" • [cyan]{p['name']}[/cyan] ([dim]{p['source']}[/dim])")
494
+ else:
495
+ click.echo(msg)
496
+ click.echo("\nPlugins to configure:")
497
+ for p in migration_data["plugins"]:
498
+ click.echo(f" • {p['name']} ({p['source']})")
499
+
500
+ if click.confirm(f"\nGenerate configuration in {output}?", default=True):
501
+ yaml_content = migrator.generate_yaml(migration_data)
502
+
503
+ # Append or write new
504
+ mode = "a" if Path(output).exists() else "w"
505
+ with open(output, mode) as f:
506
+ if mode == "a":
507
+ f.write("\n" + yaml_content)
508
+ else:
509
+ f.write(yaml_content)
510
+
511
+ if use_rich:
512
+ console.print(
513
+ Panel(
514
+ f"✅ Successfully imported stack to [bold]{output}[/bold]\n\nYou can now use it with: [green]flowyml run --stack {stack_name}[/green]",
515
+ title="Success",
516
+ style="green",
517
+ ),
518
+ )
519
+ else:
520
+ click.echo(f"✅ Successfully imported stack to {output}")
521
+ click.echo(f"You can now use it with: flowyml run --stack {stack_name}")
522
+
523
+ except ImportError:
524
+ click.echo("❌ ZenML is not installed. Install it with: pip install zenml", err=True)
525
+ except ValueError as e:
526
+ click.echo(f"❌ {e}", err=True)
527
+ except Exception as e:
528
+ click.echo(f"❌ Migration failed: {e}", err=True)
529
+
530
+
531
+ if __name__ == "__main__":
532
+ cli()
flowyml/cli/ui.py ADDED
@@ -0,0 +1,33 @@
1
+ """UI server CLI commands - Placeholder for FastAPI backend."""
2
+
3
+
4
+ def start_ui_server(host: str = "localhost", port: int = 8080, dev: bool = False) -> None:
5
+ """Start the flowyml UI server.
6
+
7
+ Args:
8
+ host: Host to bind to
9
+ port: Port to bind to
10
+ dev: Run in development mode
11
+ """
12
+ try:
13
+ import uvicorn
14
+ except ImportError:
15
+ return
16
+
17
+ # In development mode, we want reloading
18
+ reload = dev
19
+
20
+ uvicorn.run(
21
+ "flowyml.ui.backend.main:app",
22
+ host=host,
23
+ port=port,
24
+ reload=reload,
25
+ log_level="info",
26
+ )
27
+
28
+
29
+ def stop_ui_server() -> None:
30
+ """Stop the flowyml UI server."""
31
+ # Since uvicorn blocks, stopping is usually done by Ctrl+C in the terminal
32
+ # or by killing the process if run in background.
33
+ # For now, we just print a message as we don't have a daemon manager yet.
@@ -0,0 +1,68 @@
1
+ """Core pipeline execution components."""
2
+
3
+ from flowyml.core.context import Context, context
4
+ from flowyml.core.step import step, Step
5
+ from flowyml.core.pipeline import Pipeline
6
+ from flowyml.core.executor import Executor, LocalExecutor
7
+ from flowyml.core.cache import CacheStrategy
8
+ from flowyml.core.conditional import Condition, ConditionalBranch, Switch, when, unless
9
+ from flowyml.core.parallel import (
10
+ ParallelExecutor,
11
+ DataParallelExecutor,
12
+ BatchExecutor,
13
+ parallel_map,
14
+ distribute_across_gpus,
15
+ )
16
+ from flowyml.core.error_handling import (
17
+ CircuitBreaker,
18
+ ExponentialBackoff,
19
+ RetryConfig,
20
+ FallbackHandler,
21
+ retry,
22
+ on_failure,
23
+ )
24
+ from flowyml.core.resources import (
25
+ ResourceRequirements,
26
+ GPUConfig,
27
+ NodeAffinity,
28
+ resources,
29
+ )
30
+
31
+ __all__ = [
32
+ # Context
33
+ "Context",
34
+ "context",
35
+ # Steps & Pipelines
36
+ "step",
37
+ "Step",
38
+ "Pipeline",
39
+ # Execution
40
+ "Executor",
41
+ "LocalExecutor",
42
+ # Caching
43
+ "CacheStrategy",
44
+ # Conditional Execution
45
+ "Condition",
46
+ "ConditionalBranch",
47
+ "Switch",
48
+ "when",
49
+ "unless",
50
+ # Parallel Execution
51
+ "ParallelExecutor",
52
+ "DataParallelExecutor",
53
+ "BatchExecutor",
54
+ "parallel_map",
55
+ "distribute_across_gpus",
56
+ # Error Handling
57
+ "CircuitBreaker",
58
+ "ExponentialBackoff",
59
+ "RetryConfig",
60
+ "FallbackHandler",
61
+ "retry",
62
+ "on_failure",
63
+ # Resources
64
+ "ResourceRequirements",
65
+ "GPUConfig",
66
+ "NodeAffinity",
67
+ "resources",
68
+ ]