flowyml 1.5.0__py3-none-any.whl → 1.7.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 +2 -1
- flowyml/assets/featureset.py +30 -5
- flowyml/assets/metrics.py +47 -4
- flowyml/cli/main.py +397 -0
- flowyml/cli/models.py +444 -0
- flowyml/cli/rich_utils.py +95 -0
- flowyml/core/checkpoint.py +6 -1
- flowyml/core/conditional.py +104 -0
- flowyml/core/display.py +595 -0
- flowyml/core/executor.py +27 -6
- flowyml/core/orchestrator.py +500 -7
- flowyml/core/pipeline.py +447 -11
- flowyml/core/project.py +4 -1
- flowyml/core/scheduler.py +225 -81
- flowyml/core/versioning.py +13 -4
- flowyml/registry/model_registry.py +1 -1
- flowyml/ui/frontend/dist/assets/index-By4trVyv.css +1 -0
- flowyml/ui/frontend/dist/assets/{index-DF8dJaFL.js → index-CX5RV2C9.js} +118 -117
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +43 -4
- flowyml/ui/server_manager.py +189 -0
- flowyml/ui/utils.py +66 -2
- flowyml/utils/config.py +7 -0
- {flowyml-1.5.0.dist-info → flowyml-1.7.0.dist-info}/METADATA +5 -3
- {flowyml-1.5.0.dist-info → flowyml-1.7.0.dist-info}/RECORD +28 -24
- flowyml/ui/frontend/dist/assets/index-CBUXOWze.css +0 -1
- {flowyml-1.5.0.dist-info → flowyml-1.7.0.dist-info}/WHEEL +0 -0
- {flowyml-1.5.0.dist-info → flowyml-1.7.0.dist-info}/entry_points.txt +0 -0
- {flowyml-1.5.0.dist-info → flowyml-1.7.0.dist-info}/licenses/LICENSE +0 -0
flowyml/__init__.py
CHANGED
|
@@ -14,7 +14,7 @@ from flowyml.core.step import step, Step
|
|
|
14
14
|
from flowyml.core.pipeline import Pipeline
|
|
15
15
|
from flowyml.core.executor import Executor, LocalExecutor
|
|
16
16
|
from flowyml.core.cache import CacheStrategy
|
|
17
|
-
from flowyml.core.conditional import Condition, ConditionalBranch, Switch, when, unless
|
|
17
|
+
from flowyml.core.conditional import Condition, ConditionalBranch, Switch, when, unless, If
|
|
18
18
|
from flowyml.core.parallel import ParallelExecutor, DataParallelExecutor, BatchExecutor, parallel_map
|
|
19
19
|
from flowyml.core.error_handling import (
|
|
20
20
|
CircuitBreaker,
|
|
@@ -115,6 +115,7 @@ __all__ = [
|
|
|
115
115
|
"Condition",
|
|
116
116
|
"ConditionalBranch",
|
|
117
117
|
"Switch",
|
|
118
|
+
"If",
|
|
118
119
|
"when",
|
|
119
120
|
"unless",
|
|
120
121
|
# Parallel Execution
|
flowyml/assets/featureset.py
CHANGED
|
@@ -67,19 +67,36 @@ class FeatureSet(Asset):
|
|
|
67
67
|
source_dataset: Name of source dataset
|
|
68
68
|
**kwargs: Additional metadata
|
|
69
69
|
"""
|
|
70
|
-
# Initialize base asset
|
|
71
|
-
|
|
70
|
+
# Initialize base asset first
|
|
71
|
+
super().__init__(
|
|
72
72
|
name=name,
|
|
73
|
-
|
|
73
|
+
version=kwargs.get("version"),
|
|
74
|
+
data=None,
|
|
75
|
+
parent=kwargs.get("parent"),
|
|
76
|
+
tags=kwargs.get("tags"),
|
|
77
|
+
properties=kwargs.get("properties"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Create FeatureSet-specific metadata
|
|
81
|
+
feature_set_metadata = FeatureSetMetadata(
|
|
82
|
+
asset_id=self.asset_id,
|
|
83
|
+
name=name,
|
|
84
|
+
version=self.version,
|
|
85
|
+
asset_type=self.__class__.__name__,
|
|
86
|
+
created_at=self.metadata.created_at,
|
|
87
|
+
created_by=self.metadata.created_by,
|
|
88
|
+
parent_ids=self.metadata.parent_ids,
|
|
89
|
+
tags=self.metadata.tags,
|
|
90
|
+
properties=self.metadata.properties,
|
|
74
91
|
feature_names=feature_names or [],
|
|
75
92
|
num_features=len(feature_names) if feature_names else 0,
|
|
76
93
|
num_samples=num_samples,
|
|
77
94
|
transformations=transformations or [],
|
|
78
95
|
source_dataset=source_dataset,
|
|
79
|
-
**kwargs,
|
|
80
96
|
)
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
# Replace metadata with FeatureSet-specific metadata
|
|
99
|
+
self.metadata = feature_set_metadata
|
|
83
100
|
self._data = data
|
|
84
101
|
|
|
85
102
|
# Extract feature metadata if data provided
|
|
@@ -147,6 +164,11 @@ class FeatureSet(Asset):
|
|
|
147
164
|
"""Get the feature data."""
|
|
148
165
|
return self._data
|
|
149
166
|
|
|
167
|
+
@data.setter
|
|
168
|
+
def data(self, value: Any) -> None:
|
|
169
|
+
"""Set the feature data."""
|
|
170
|
+
self._data = value
|
|
171
|
+
|
|
150
172
|
@property
|
|
151
173
|
def feature_names(self) -> list[str]:
|
|
152
174
|
"""Get feature names."""
|
|
@@ -188,6 +210,7 @@ class FeatureSet(Asset):
|
|
|
188
210
|
data: Any,
|
|
189
211
|
name: str | None = None,
|
|
190
212
|
feature_names: list[str] | None = None,
|
|
213
|
+
num_samples: int = 0,
|
|
191
214
|
transformations: list[str] | None = None,
|
|
192
215
|
source_dataset: str | None = None,
|
|
193
216
|
**kwargs,
|
|
@@ -198,6 +221,7 @@ class FeatureSet(Asset):
|
|
|
198
221
|
data: The feature matrix
|
|
199
222
|
name: Name of the feature set (auto-generated if not provided)
|
|
200
223
|
feature_names: List of feature names
|
|
224
|
+
num_samples: Number of samples in the feature set
|
|
201
225
|
transformations: List of transformations applied
|
|
202
226
|
source_dataset: Name of source dataset
|
|
203
227
|
**kwargs: Additional metadata
|
|
@@ -213,6 +237,7 @@ class FeatureSet(Asset):
|
|
|
213
237
|
name=name,
|
|
214
238
|
data=data,
|
|
215
239
|
feature_names=feature_names,
|
|
240
|
+
num_samples=num_samples,
|
|
216
241
|
transformations=transformations,
|
|
217
242
|
source_dataset=source_dataset,
|
|
218
243
|
**kwargs,
|
flowyml/assets/metrics.py
CHANGED
|
@@ -93,18 +93,61 @@ class Metrics(Asset):
|
|
|
93
93
|
parent: Asset | None = None,
|
|
94
94
|
tags: dict[str, str] | None = None,
|
|
95
95
|
properties: dict[str, Any] | None = None,
|
|
96
|
-
|
|
96
|
+
metadata: dict[str, Any] | None = None,
|
|
97
|
+
metrics: dict[str, Any] | None = None,
|
|
98
|
+
**kwargs,
|
|
97
99
|
) -> "Metrics":
|
|
98
100
|
"""Factory method to create metrics.
|
|
99
101
|
|
|
102
|
+
Supports multiple ways to provide metrics:
|
|
103
|
+
1. As keyword arguments: Metrics.create(accuracy=0.95, loss=0.05)
|
|
104
|
+
2. As a dict: Metrics.create(metrics={"accuracy": 0.95, "loss": 0.05})
|
|
105
|
+
3. Mixed: Metrics.create(metrics={"accuracy": 0.95}, loss=0.05)
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
name: Name of the metrics asset
|
|
109
|
+
version: Version string
|
|
110
|
+
parent: Parent asset for lineage
|
|
111
|
+
tags: Tags dictionary (or use metadata for convenience)
|
|
112
|
+
properties: Properties dictionary
|
|
113
|
+
metadata: Metadata dictionary (merged into tags and properties)
|
|
114
|
+
metrics: Metrics as a dictionary (alternative to **kwargs)
|
|
115
|
+
**kwargs: Additional metrics as keyword arguments
|
|
116
|
+
|
|
100
117
|
Example:
|
|
118
|
+
>>> # Using keyword arguments
|
|
101
119
|
>>> metrics = Metrics.create(accuracy=0.95, loss=0.05, training_time="2h 15m")
|
|
120
|
+
|
|
121
|
+
>>> # Using metrics dict
|
|
122
|
+
>>> metrics = Metrics.create(
|
|
123
|
+
... name="example_metrics",
|
|
124
|
+
... metrics={"test_accuracy": 0.93, "test_loss": 0.07},
|
|
125
|
+
... metadata={"source": "example"},
|
|
126
|
+
... )
|
|
102
127
|
"""
|
|
128
|
+
# Merge metrics dict with kwargs
|
|
129
|
+
all_metrics = {}
|
|
130
|
+
if metrics:
|
|
131
|
+
all_metrics.update(metrics)
|
|
132
|
+
all_metrics.update(kwargs)
|
|
133
|
+
|
|
134
|
+
# Handle metadata - merge into tags and properties
|
|
135
|
+
final_tags = tags or {}
|
|
136
|
+
final_properties = properties or {}
|
|
137
|
+
if metadata:
|
|
138
|
+
# If metadata contains string values, treat as tags
|
|
139
|
+
# Otherwise, merge into properties
|
|
140
|
+
for key, value in metadata.items():
|
|
141
|
+
if isinstance(value, str):
|
|
142
|
+
final_tags[key] = value
|
|
143
|
+
else:
|
|
144
|
+
final_properties[key] = value
|
|
145
|
+
|
|
103
146
|
return cls(
|
|
104
147
|
name=name or "metrics",
|
|
105
148
|
version=version,
|
|
106
|
-
data=
|
|
149
|
+
data=all_metrics if all_metrics else None,
|
|
107
150
|
parent=parent,
|
|
108
|
-
tags=
|
|
109
|
-
properties=
|
|
151
|
+
tags=final_tags,
|
|
152
|
+
properties=final_properties,
|
|
110
153
|
)
|
flowyml/cli/main.py
CHANGED
|
@@ -4,6 +4,14 @@ import click
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from flowyml.utils.config import get_config
|
|
6
6
|
|
|
7
|
+
# Import model commands early to avoid E402 error
|
|
8
|
+
from flowyml.cli.models import (
|
|
9
|
+
list_models,
|
|
10
|
+
promote_model,
|
|
11
|
+
show_model,
|
|
12
|
+
delete_model,
|
|
13
|
+
)
|
|
14
|
+
|
|
7
15
|
|
|
8
16
|
@click.group()
|
|
9
17
|
@click.version_option(version="0.1.0", prog_name="flowyml")
|
|
@@ -362,6 +370,19 @@ def clear() -> None:
|
|
|
362
370
|
click.echo(f"✗ Error clearing cache: {e}", err=True)
|
|
363
371
|
|
|
364
372
|
|
|
373
|
+
@cli.group()
|
|
374
|
+
def models() -> None:
|
|
375
|
+
"""Model registry management commands."""
|
|
376
|
+
pass
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# Register model commands
|
|
380
|
+
models.add_command(list_models)
|
|
381
|
+
models.add_command(promote_model)
|
|
382
|
+
models.add_command(show_model)
|
|
383
|
+
models.add_command(delete_model)
|
|
384
|
+
|
|
385
|
+
|
|
365
386
|
@cli.group()
|
|
366
387
|
def config() -> None:
|
|
367
388
|
"""Configuration management commands."""
|
|
@@ -429,5 +450,381 @@ def logs(run_id: str, step: str, tail: int) -> None:
|
|
|
429
450
|
click.echo(" [Log output would appear here]")
|
|
430
451
|
|
|
431
452
|
|
|
453
|
+
# ============================================================================
|
|
454
|
+
# Quick Commands: flowyml go / stop / status
|
|
455
|
+
# ============================================================================
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@cli.command()
|
|
459
|
+
@click.option("--host", default="localhost", help="Host to bind to")
|
|
460
|
+
@click.option("--port", default=8080, type=int, help="Port to bind to")
|
|
461
|
+
@click.option("--open-browser", "-o", is_flag=True, help="Open browser automatically")
|
|
462
|
+
def go(host: str, port: int, open_browser: bool) -> None:
|
|
463
|
+
r"""🚀 Start flowyml - Initialize UI dashboard and show welcome message.
|
|
464
|
+
|
|
465
|
+
This is the quickest way to get started with flowyml. It starts the UI
|
|
466
|
+
dashboard server in the background and displays the URL to access it.
|
|
467
|
+
|
|
468
|
+
\b
|
|
469
|
+
Examples:
|
|
470
|
+
flowyml go # Start on default port 8080
|
|
471
|
+
flowyml go -o # Start and open browser
|
|
472
|
+
flowyml go --port 9000 # Start on custom port
|
|
473
|
+
"""
|
|
474
|
+
import subprocess
|
|
475
|
+
import sys
|
|
476
|
+
import time
|
|
477
|
+
from flowyml.ui.utils import is_ui_running
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
from rich.console import Console
|
|
481
|
+
from rich.panel import Panel
|
|
482
|
+
from rich.text import Text
|
|
483
|
+
from rich import box
|
|
484
|
+
|
|
485
|
+
console = Console()
|
|
486
|
+
rich_available = True
|
|
487
|
+
except ImportError:
|
|
488
|
+
rich_available = False
|
|
489
|
+
|
|
490
|
+
url = f"http://{host}:{port}"
|
|
491
|
+
|
|
492
|
+
# Check if already running
|
|
493
|
+
if is_ui_running(host, port):
|
|
494
|
+
if rich_available:
|
|
495
|
+
panel_content = Text()
|
|
496
|
+
panel_content.append("✅ ", style="green")
|
|
497
|
+
panel_content.append("flowyml is already running!\n\n", style="bold green")
|
|
498
|
+
panel_content.append("🌐 Dashboard: ", style="bold")
|
|
499
|
+
panel_content.append(url, style="cyan underline link " + url)
|
|
500
|
+
panel_content.append("\n\n", style="")
|
|
501
|
+
panel_content.append("Run ", style="dim")
|
|
502
|
+
panel_content.append("flowyml stop", style="bold yellow")
|
|
503
|
+
panel_content.append(" to stop the server.", style="dim")
|
|
504
|
+
|
|
505
|
+
console.print(
|
|
506
|
+
Panel(
|
|
507
|
+
panel_content,
|
|
508
|
+
title="[bold cyan]🌊 flowyml[/bold cyan]",
|
|
509
|
+
border_style="cyan",
|
|
510
|
+
box=box.DOUBLE,
|
|
511
|
+
),
|
|
512
|
+
)
|
|
513
|
+
else:
|
|
514
|
+
click.echo("✅ flowyml is already running!")
|
|
515
|
+
click.echo(f"🌐 Dashboard: {url}")
|
|
516
|
+
click.echo("\nRun 'flowyml stop' to stop the server.")
|
|
517
|
+
|
|
518
|
+
if open_browser:
|
|
519
|
+
import webbrowser
|
|
520
|
+
|
|
521
|
+
webbrowser.open(url)
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
# Start the UI server as a background subprocess
|
|
525
|
+
if rich_available:
|
|
526
|
+
console.print("[bold cyan]🌊 flowyml[/bold cyan] - Starting up...\n")
|
|
527
|
+
else:
|
|
528
|
+
click.echo("🌊 flowyml - Starting up...")
|
|
529
|
+
|
|
530
|
+
try:
|
|
531
|
+
# Start uvicorn as a background process
|
|
532
|
+
# Using subprocess with nohup-like behavior
|
|
533
|
+
cmd = [
|
|
534
|
+
sys.executable,
|
|
535
|
+
"-m",
|
|
536
|
+
"uvicorn",
|
|
537
|
+
"flowyml.ui.backend.main:app",
|
|
538
|
+
"--host",
|
|
539
|
+
host,
|
|
540
|
+
"--port",
|
|
541
|
+
str(port),
|
|
542
|
+
"--log-level",
|
|
543
|
+
"warning",
|
|
544
|
+
]
|
|
545
|
+
|
|
546
|
+
# Start as detached background process
|
|
547
|
+
if sys.platform == "win32":
|
|
548
|
+
# Windows: use CREATE_NEW_PROCESS_GROUP
|
|
549
|
+
process = subprocess.Popen(
|
|
550
|
+
cmd,
|
|
551
|
+
stdout=subprocess.DEVNULL,
|
|
552
|
+
stderr=subprocess.DEVNULL,
|
|
553
|
+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
|
|
554
|
+
)
|
|
555
|
+
else:
|
|
556
|
+
# Unix: use start_new_session
|
|
557
|
+
process = subprocess.Popen(
|
|
558
|
+
cmd,
|
|
559
|
+
stdout=subprocess.DEVNULL,
|
|
560
|
+
stderr=subprocess.DEVNULL,
|
|
561
|
+
start_new_session=True,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# Wait for server to start (up to 8 seconds)
|
|
565
|
+
started = False
|
|
566
|
+
for _ in range(80):
|
|
567
|
+
time.sleep(0.1)
|
|
568
|
+
if is_ui_running(host, port):
|
|
569
|
+
started = True
|
|
570
|
+
break
|
|
571
|
+
|
|
572
|
+
if started:
|
|
573
|
+
# Save PID for later stop command
|
|
574
|
+
pid_file = Path.home() / ".flowyml" / "ui_server.pid"
|
|
575
|
+
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
576
|
+
pid_file.write_text(f"{process.pid}\n{host}\n{port}")
|
|
577
|
+
|
|
578
|
+
if rich_available:
|
|
579
|
+
panel_content = Text()
|
|
580
|
+
panel_content.append("✅ ", style="green")
|
|
581
|
+
panel_content.append("flowyml is ready!\n\n", style="bold green")
|
|
582
|
+
panel_content.append("🌐 Dashboard: ", style="bold")
|
|
583
|
+
panel_content.append(url, style="cyan underline link " + url)
|
|
584
|
+
panel_content.append("\n\n", style="")
|
|
585
|
+
panel_content.append("📊 View pipelines: ", style="")
|
|
586
|
+
panel_content.append(f"{url}/pipelines", style="cyan")
|
|
587
|
+
panel_content.append("\n", style="")
|
|
588
|
+
panel_content.append("📜 View runs: ", style="")
|
|
589
|
+
panel_content.append(f"{url}/runs", style="cyan")
|
|
590
|
+
panel_content.append("\n\n", style="")
|
|
591
|
+
panel_content.append("Run ", style="dim")
|
|
592
|
+
panel_content.append("flowyml stop", style="bold yellow")
|
|
593
|
+
panel_content.append(" to stop the server.", style="dim")
|
|
594
|
+
|
|
595
|
+
console.print(
|
|
596
|
+
Panel(
|
|
597
|
+
panel_content,
|
|
598
|
+
title="[bold cyan]🌊 flowyml[/bold cyan]",
|
|
599
|
+
border_style="green",
|
|
600
|
+
box=box.DOUBLE,
|
|
601
|
+
),
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
console.print()
|
|
605
|
+
console.print("[dim]Tip: The dashboard runs in the background. Your pipelines will[/dim]")
|
|
606
|
+
console.print("[dim]automatically show a clickable URL when they run.[/dim]")
|
|
607
|
+
else:
|
|
608
|
+
click.echo("✅ flowyml is ready!")
|
|
609
|
+
click.echo(f"🌐 Dashboard: {url}")
|
|
610
|
+
click.echo(f"📊 View pipelines: {url}/pipelines")
|
|
611
|
+
click.echo(f"📜 View runs: {url}/runs")
|
|
612
|
+
click.echo("\nRun 'flowyml stop' to stop the server.")
|
|
613
|
+
click.echo("\nTip: The dashboard runs in the background. Your pipelines will")
|
|
614
|
+
click.echo("automatically show a clickable URL when they run.")
|
|
615
|
+
|
|
616
|
+
if open_browser:
|
|
617
|
+
import webbrowser
|
|
618
|
+
|
|
619
|
+
webbrowser.open(url)
|
|
620
|
+
else:
|
|
621
|
+
# Server didn't start, kill the process
|
|
622
|
+
process.terminate()
|
|
623
|
+
raise RuntimeError("Server failed to start within timeout")
|
|
624
|
+
|
|
625
|
+
except Exception as e:
|
|
626
|
+
if rich_available:
|
|
627
|
+
panel_content = Text()
|
|
628
|
+
panel_content.append("❌ ", style="red")
|
|
629
|
+
panel_content.append("Failed to start flowyml UI server.\n\n", style="bold red")
|
|
630
|
+
panel_content.append(f"Error: {str(e)[:100]}\n\n", style="dim red")
|
|
631
|
+
panel_content.append("Possible issues:\n", style="")
|
|
632
|
+
panel_content.append(f" • Port {port} might be in use\n", style="dim")
|
|
633
|
+
panel_content.append(" • Missing dependencies (uvicorn, fastapi)\n", style="dim")
|
|
634
|
+
panel_content.append("\n", style="")
|
|
635
|
+
panel_content.append("Try:\n", style="")
|
|
636
|
+
panel_content.append(f" flowyml go --port {port + 1}", style="bold yellow")
|
|
637
|
+
panel_content.append(" (use different port)\n", style="dim")
|
|
638
|
+
panel_content.append(" flowyml ui start", style="bold yellow")
|
|
639
|
+
panel_content.append(" (for verbose output)", style="dim")
|
|
640
|
+
|
|
641
|
+
console.print(
|
|
642
|
+
Panel(
|
|
643
|
+
panel_content,
|
|
644
|
+
title="[bold red]Error[/bold red]",
|
|
645
|
+
border_style="red",
|
|
646
|
+
box=box.ROUNDED,
|
|
647
|
+
),
|
|
648
|
+
)
|
|
649
|
+
else:
|
|
650
|
+
click.echo(f"❌ Failed to start flowyml UI server: {e}")
|
|
651
|
+
click.echo("Possible issues:")
|
|
652
|
+
click.echo(f" • Port {port} might be in use")
|
|
653
|
+
click.echo(" • Missing dependencies (uvicorn, fastapi)")
|
|
654
|
+
click.echo(f"\nTry: flowyml go --port {port + 1}")
|
|
655
|
+
click.echo("Or run 'flowyml ui start' for verbose output.")
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
@cli.command("stop")
|
|
659
|
+
@click.option("--host", default="localhost", help="Host of the server")
|
|
660
|
+
@click.option("--port", default=8080, type=int, help="Port of the server")
|
|
661
|
+
def stop_server(host: str, port: int) -> None:
|
|
662
|
+
r"""🛑 Stop flowyml - Shutdown the UI dashboard server.
|
|
663
|
+
|
|
664
|
+
Stops the flowyml UI server if it's running.
|
|
665
|
+
|
|
666
|
+
\b
|
|
667
|
+
Examples:
|
|
668
|
+
flowyml stop # Stop server on default port
|
|
669
|
+
flowyml stop --port 9000 # Stop server on custom port
|
|
670
|
+
"""
|
|
671
|
+
import os
|
|
672
|
+
import signal
|
|
673
|
+
import time
|
|
674
|
+
from flowyml.ui.utils import is_ui_running
|
|
675
|
+
|
|
676
|
+
try:
|
|
677
|
+
from rich.console import Console
|
|
678
|
+
from rich.panel import Panel
|
|
679
|
+
from rich.text import Text
|
|
680
|
+
from rich import box
|
|
681
|
+
|
|
682
|
+
console = Console()
|
|
683
|
+
rich_available = True
|
|
684
|
+
except ImportError:
|
|
685
|
+
rich_available = False
|
|
686
|
+
|
|
687
|
+
pid_file = Path.home() / ".flowyml" / "ui_server.pid"
|
|
688
|
+
|
|
689
|
+
# First check if we have a PID file from 'flowyml go'
|
|
690
|
+
if pid_file.exists():
|
|
691
|
+
try:
|
|
692
|
+
content = pid_file.read_text().strip().split("\n")
|
|
693
|
+
pid = int(content[0])
|
|
694
|
+
# Note: saved_host and saved_port are in the file but we use the CLI args
|
|
695
|
+
# to allow stopping a server on a different port if needed
|
|
696
|
+
|
|
697
|
+
# Try to kill the process
|
|
698
|
+
try:
|
|
699
|
+
os.kill(pid, signal.SIGTERM)
|
|
700
|
+
time.sleep(0.5)
|
|
701
|
+
|
|
702
|
+
# Clean up PID file
|
|
703
|
+
pid_file.unlink(missing_ok=True)
|
|
704
|
+
|
|
705
|
+
if rich_available:
|
|
706
|
+
console.print(f"[green]✅ flowyml server (PID {pid}) stopped successfully.[/green]")
|
|
707
|
+
else:
|
|
708
|
+
click.echo(f"✅ flowyml server (PID {pid}) stopped successfully.")
|
|
709
|
+
return
|
|
710
|
+
except ProcessLookupError:
|
|
711
|
+
# Process already dead, clean up PID file
|
|
712
|
+
pid_file.unlink(missing_ok=True)
|
|
713
|
+
except PermissionError:
|
|
714
|
+
if rich_available:
|
|
715
|
+
console.print(f"[red]❌ Permission denied to stop process {pid}[/red]")
|
|
716
|
+
else:
|
|
717
|
+
click.echo(f"❌ Permission denied to stop process {pid}")
|
|
718
|
+
return
|
|
719
|
+
except (ValueError, IndexError):
|
|
720
|
+
# Invalid PID file, remove it
|
|
721
|
+
pid_file.unlink(missing_ok=True)
|
|
722
|
+
|
|
723
|
+
# Check if server is running
|
|
724
|
+
if not is_ui_running(host, port):
|
|
725
|
+
if rich_available:
|
|
726
|
+
console.print(f"[yellow]ℹ️ No flowyml server running on {host}:{port}[/yellow]")
|
|
727
|
+
else:
|
|
728
|
+
click.echo(f"ℹ️ No flowyml server running on {host}:{port}")
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
# Server is running but we don't have a PID file - must be from 'flowyml ui start'
|
|
732
|
+
if rich_available:
|
|
733
|
+
panel_content = Text()
|
|
734
|
+
panel_content.append("ℹ️ ", style="yellow")
|
|
735
|
+
panel_content.append("Server running but not started with 'flowyml go'.\n\n", style="")
|
|
736
|
+
panel_content.append("To stop it:\n", style="")
|
|
737
|
+
panel_content.append(" • If running in foreground: ", style="dim")
|
|
738
|
+
panel_content.append("Press Ctrl+C\n", style="bold")
|
|
739
|
+
panel_content.append(" • Find and kill: ", style="dim")
|
|
740
|
+
panel_content.append(f"pkill -f 'uvicorn.*:{port}'\n", style="bold")
|
|
741
|
+
panel_content.append(" • Or find PID: ", style="dim")
|
|
742
|
+
panel_content.append(f"lsof -i :{port}", style="bold")
|
|
743
|
+
|
|
744
|
+
console.print(
|
|
745
|
+
Panel(
|
|
746
|
+
panel_content,
|
|
747
|
+
title="[bold yellow]Manual Stop Required[/bold yellow]",
|
|
748
|
+
border_style="yellow",
|
|
749
|
+
box=box.ROUNDED,
|
|
750
|
+
),
|
|
751
|
+
)
|
|
752
|
+
else:
|
|
753
|
+
click.echo("ℹ️ Server running but not started with 'flowyml go'.")
|
|
754
|
+
click.echo("To stop it:")
|
|
755
|
+
click.echo(" • If running in foreground: Press Ctrl+C")
|
|
756
|
+
click.echo(f" • Find and kill: pkill -f 'uvicorn.*:{port}'")
|
|
757
|
+
click.echo(f" • Or find PID: lsof -i :{port}")
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
@cli.command("status")
|
|
761
|
+
@click.option("--host", default="localhost", help="Host to check")
|
|
762
|
+
@click.option("--port", default=8080, type=int, help="Port to check")
|
|
763
|
+
def server_status(host: str, port: int) -> None:
|
|
764
|
+
r"""📊 Check flowyml status - Show if the UI server is running.
|
|
765
|
+
|
|
766
|
+
\b
|
|
767
|
+
Examples:
|
|
768
|
+
flowyml status # Check default port
|
|
769
|
+
flowyml status --port 9000 # Check custom port
|
|
770
|
+
"""
|
|
771
|
+
from flowyml.ui.utils import is_ui_running
|
|
772
|
+
|
|
773
|
+
try:
|
|
774
|
+
from rich.console import Console
|
|
775
|
+
from rich.panel import Panel
|
|
776
|
+
from rich.text import Text
|
|
777
|
+
from rich import box
|
|
778
|
+
|
|
779
|
+
console = Console()
|
|
780
|
+
rich_available = True
|
|
781
|
+
except ImportError:
|
|
782
|
+
rich_available = False
|
|
783
|
+
|
|
784
|
+
if is_ui_running(host, port):
|
|
785
|
+
url = f"http://{host}:{port}"
|
|
786
|
+
if rich_available:
|
|
787
|
+
panel_content = Text()
|
|
788
|
+
panel_content.append("✅ ", style="green")
|
|
789
|
+
panel_content.append("flowyml is running\n\n", style="bold green")
|
|
790
|
+
panel_content.append("🌐 Dashboard: ", style="bold")
|
|
791
|
+
panel_content.append(url, style="cyan underline link " + url)
|
|
792
|
+
panel_content.append("\n", style="")
|
|
793
|
+
panel_content.append("💚 Health: ", style="")
|
|
794
|
+
panel_content.append(f"{url}/api/health", style="dim")
|
|
795
|
+
|
|
796
|
+
console.print(
|
|
797
|
+
Panel(
|
|
798
|
+
panel_content,
|
|
799
|
+
title="[bold cyan]🌊 flowyml Status[/bold cyan]",
|
|
800
|
+
border_style="green",
|
|
801
|
+
box=box.ROUNDED,
|
|
802
|
+
),
|
|
803
|
+
)
|
|
804
|
+
else:
|
|
805
|
+
click.echo("✅ flowyml is running")
|
|
806
|
+
click.echo(f"🌐 Dashboard: {url}")
|
|
807
|
+
click.echo(f"💚 Health: {url}/api/health")
|
|
808
|
+
else:
|
|
809
|
+
if rich_available:
|
|
810
|
+
panel_content = Text()
|
|
811
|
+
panel_content.append("❌ ", style="red")
|
|
812
|
+
panel_content.append(f"flowyml is not running on {host}:{port}\n\n", style="")
|
|
813
|
+
panel_content.append("Start with: ", style="dim")
|
|
814
|
+
panel_content.append("flowyml go", style="bold cyan")
|
|
815
|
+
|
|
816
|
+
console.print(
|
|
817
|
+
Panel(
|
|
818
|
+
panel_content,
|
|
819
|
+
title="[bold cyan]🌊 flowyml Status[/bold cyan]",
|
|
820
|
+
border_style="red",
|
|
821
|
+
box=box.ROUNDED,
|
|
822
|
+
),
|
|
823
|
+
)
|
|
824
|
+
else:
|
|
825
|
+
click.echo(f"❌ flowyml is not running on {host}:{port}")
|
|
826
|
+
click.echo("Start with: flowyml go")
|
|
827
|
+
|
|
828
|
+
|
|
432
829
|
if __name__ == "__main__":
|
|
433
830
|
cli()
|