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/stack_cli.py
ADDED
|
@@ -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.
|
flowyml/core/__init__.py
ADDED
|
@@ -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
|
+
]
|