flowyml 1.4.0__py3-none-any.whl → 1.6.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 +21 -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 +525 -0
- flowyml/core/execution_status.py +1 -0
- flowyml/core/executor.py +201 -8
- flowyml/core/orchestrator.py +500 -7
- flowyml/core/pipeline.py +301 -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/storage/sql.py +53 -13
- flowyml/ui/backend/main.py +2 -0
- flowyml/ui/backend/routers/assets.py +36 -0
- flowyml/ui/backend/routers/execution.py +2 -2
- flowyml/ui/backend/routers/runs.py +211 -0
- flowyml/ui/backend/routers/stats.py +2 -2
- flowyml/ui/backend/routers/websocket.py +121 -0
- flowyml/ui/frontend/dist/assets/index-By4trVyv.css +1 -0
- flowyml/ui/frontend/dist/assets/index-CX5RV2C9.js +630 -0
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/package-lock.json +289 -0
- flowyml/ui/frontend/package.json +1 -0
- flowyml/ui/frontend/src/app/compare/page.jsx +213 -0
- flowyml/ui/frontend/src/app/experiments/compare/page.jsx +289 -0
- flowyml/ui/frontend/src/app/experiments/page.jsx +61 -1
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +418 -203
- flowyml/ui/frontend/src/app/runs/page.jsx +64 -3
- flowyml/ui/frontend/src/app/settings/page.jsx +1 -1
- flowyml/ui/frontend/src/app/tokens/page.jsx +8 -6
- flowyml/ui/frontend/src/components/ArtifactViewer.jsx +159 -0
- flowyml/ui/frontend/src/components/NavigationTree.jsx +26 -9
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +69 -28
- flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +42 -14
- flowyml/ui/frontend/src/router/index.jsx +4 -0
- flowyml/ui/server_manager.py +181 -0
- flowyml/ui/utils.py +63 -1
- flowyml/utils/config.py +7 -0
- {flowyml-1.4.0.dist-info → flowyml-1.6.0.dist-info}/METADATA +5 -3
- {flowyml-1.4.0.dist-info → flowyml-1.6.0.dist-info}/RECORD +49 -41
- flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +0 -1
- flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +0 -592
- {flowyml-1.4.0.dist-info → flowyml-1.6.0.dist-info}/WHEEL +0 -0
- {flowyml-1.4.0.dist-info → flowyml-1.6.0.dist-info}/entry_points.txt +0 -0
- {flowyml-1.4.0.dist-info → flowyml-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -27,6 +27,7 @@ import { ProjectSelector } from './ProjectSelector';
|
|
|
27
27
|
|
|
28
28
|
export function RunDetailsPanel({ run, onClose }) {
|
|
29
29
|
const [details, setDetails] = useState(null);
|
|
30
|
+
const [artifacts, setArtifacts] = useState([]);
|
|
30
31
|
const [loading, setLoading] = useState(false);
|
|
31
32
|
const [activeTab, setActiveTab] = useState('overview'); // overview, steps, artifacts
|
|
32
33
|
const [currentProject, setCurrentProject] = useState(run?.project);
|
|
@@ -41,9 +42,17 @@ export function RunDetailsPanel({ run, onClose }) {
|
|
|
41
42
|
const fetchRunDetails = async () => {
|
|
42
43
|
setLoading(true);
|
|
43
44
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
45
|
+
const [runRes, assetsRes] = await Promise.all([
|
|
46
|
+
fetchApi(`/api/runs/${run.run_id}`),
|
|
47
|
+
fetchApi(`/api/assets?run_id=${run.run_id}`)
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const data = await runRes.json();
|
|
51
|
+
const assetsData = await assetsRes.json();
|
|
52
|
+
|
|
46
53
|
setDetails(data);
|
|
54
|
+
setArtifacts(assetsData.assets || []);
|
|
55
|
+
|
|
47
56
|
if (data.project) setCurrentProject(data.project);
|
|
48
57
|
} catch (error) {
|
|
49
58
|
console.error('Failed to fetch run details:', error);
|
|
@@ -177,11 +186,11 @@ export function RunDetailsPanel({ run, onClose }) {
|
|
|
177
186
|
{activeTab === 'overview' && (
|
|
178
187
|
<div className="space-y-4">
|
|
179
188
|
{/* DAG Visualization Preview */}
|
|
180
|
-
<
|
|
181
|
-
<div className="p-3 border-b border-slate-100 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50 flex justify-between items-center">
|
|
189
|
+
<div className="rounded-xl border border-slate-200 dark:border-slate-700 overflow-hidden h-[400px] flex flex-col bg-white dark:bg-slate-800">
|
|
190
|
+
<div className="p-3 border-b border-slate-100 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50 flex justify-between items-center shrink-0">
|
|
182
191
|
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300">Pipeline Graph</h3>
|
|
183
192
|
</div>
|
|
184
|
-
<div className="h-
|
|
193
|
+
<div className="flex-1 min-h-0 bg-slate-50/50 dark:bg-slate-900/50">
|
|
185
194
|
{runData.dag ? (
|
|
186
195
|
<PipelineGraph
|
|
187
196
|
dag={runData.dag}
|
|
@@ -193,7 +202,7 @@ export function RunDetailsPanel({ run, onClose }) {
|
|
|
193
202
|
</div>
|
|
194
203
|
)}
|
|
195
204
|
</div>
|
|
196
|
-
</
|
|
205
|
+
</div>
|
|
197
206
|
|
|
198
207
|
{/* Error Display if Failed */}
|
|
199
208
|
{runData.status === 'failed' && runData.error && (
|
|
@@ -238,14 +247,33 @@ export function RunDetailsPanel({ run, onClose }) {
|
|
|
238
247
|
|
|
239
248
|
{activeTab === 'artifacts' && (
|
|
240
249
|
<div className="space-y-3">
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
{artifacts.length > 0 ? (
|
|
251
|
+
artifacts.map(art => (
|
|
252
|
+
<div
|
|
253
|
+
key={art.artifact_id}
|
|
254
|
+
className="flex items-center gap-3 p-3 bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-primary-300 dark:hover:border-primary-700 transition-colors"
|
|
255
|
+
>
|
|
256
|
+
<div className="p-2 bg-slate-50 dark:bg-slate-700 rounded-md text-slate-500 dark:text-slate-400">
|
|
257
|
+
<FileText size={18} />
|
|
258
|
+
</div>
|
|
259
|
+
<div className="min-w-0 flex-1">
|
|
260
|
+
<p className="text-sm font-semibold text-slate-900 dark:text-white truncate">
|
|
261
|
+
{art.name}
|
|
262
|
+
</p>
|
|
263
|
+
<p className="text-xs text-slate-500 truncate">{art.type}</p>
|
|
264
|
+
</div>
|
|
265
|
+
<Link to={`/runs/${run.run_id}`}>
|
|
266
|
+
<Button variant="ghost" size="sm">
|
|
267
|
+
<ArrowRight size={14} />
|
|
268
|
+
</Button>
|
|
269
|
+
</Link>
|
|
270
|
+
</div>
|
|
271
|
+
))
|
|
272
|
+
) : (
|
|
273
|
+
<div className="text-center py-8 text-slate-500">
|
|
274
|
+
No artifacts found for this run.
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
249
277
|
</div>
|
|
250
278
|
)}
|
|
251
279
|
</>
|
|
@@ -16,6 +16,8 @@ import { Leaderboard } from '../app/leaderboard/page';
|
|
|
16
16
|
import { Plugins } from '../app/plugins/page';
|
|
17
17
|
import { Settings } from '../app/settings/page';
|
|
18
18
|
import { TokenManagement } from '../app/tokens/page';
|
|
19
|
+
import { RunComparisonPage } from '../app/compare/page';
|
|
20
|
+
import { ExperimentComparisonPage } from '../app/experiments/compare/page';
|
|
19
21
|
|
|
20
22
|
export const router = createBrowserRouter([
|
|
21
23
|
{
|
|
@@ -25,9 +27,11 @@ export const router = createBrowserRouter([
|
|
|
25
27
|
{ index: true, element: <Dashboard /> },
|
|
26
28
|
{ path: 'pipelines', element: <Pipelines /> },
|
|
27
29
|
{ path: 'runs', element: <Runs /> },
|
|
30
|
+
{ path: 'compare', element: <RunComparisonPage /> },
|
|
28
31
|
{ path: 'runs/:runId', element: <RunDetails /> },
|
|
29
32
|
{ path: 'assets', element: <Assets /> },
|
|
30
33
|
{ path: 'experiments', element: <Experiments /> },
|
|
34
|
+
{ path: 'experiments/compare', element: <ExperimentComparisonPage /> },
|
|
31
35
|
{ path: 'experiments/:experimentId', element: <ExperimentDetails /> },
|
|
32
36
|
{ path: 'traces', element: <Traces /> },
|
|
33
37
|
{ path: 'projects', element: <Projects /> },
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""UI Server Manager - Auto-start and manage the UI server in background."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from flowyml.ui.utils import is_ui_running, get_ui_url, get_ui_host_port
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UIServerManager:
|
|
12
|
+
"""Manages the UI server lifecycle in background threads."""
|
|
13
|
+
|
|
14
|
+
_instance: Optional["UIServerManager"] = None
|
|
15
|
+
_lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._server_thread: Optional[threading.Thread] = None
|
|
19
|
+
self._server_process: Optional[subprocess.Popen] = None
|
|
20
|
+
# Initialize from config/env vars
|
|
21
|
+
self._host, self._port = get_ui_host_port()
|
|
22
|
+
self._running = False
|
|
23
|
+
self._started = False
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def get_instance(cls) -> "UIServerManager":
|
|
27
|
+
"""Get singleton instance of UI server manager."""
|
|
28
|
+
if cls._instance is None:
|
|
29
|
+
with cls._lock:
|
|
30
|
+
if cls._instance is None:
|
|
31
|
+
cls._instance = cls()
|
|
32
|
+
return cls._instance
|
|
33
|
+
|
|
34
|
+
def ensure_running(self, host: str | None = None, port: int | None = None, auto_start: bool = True) -> bool:
|
|
35
|
+
"""Ensure UI server is running, start it if not and auto_start is True.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
host: Host to bind to (uses config/env if None)
|
|
39
|
+
port: Port to bind to (uses config/env if None)
|
|
40
|
+
auto_start: If True, automatically start server if not running
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if server is running, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
# Use provided values or get from config
|
|
46
|
+
if host is None or port is None:
|
|
47
|
+
config_host, config_port = get_ui_host_port()
|
|
48
|
+
self._host = host if host is not None else config_host
|
|
49
|
+
self._port = port if port is not None else config_port
|
|
50
|
+
else:
|
|
51
|
+
self._host = host
|
|
52
|
+
self._port = port
|
|
53
|
+
|
|
54
|
+
# Check if already running
|
|
55
|
+
if is_ui_running(host, port):
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
if not auto_start:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
# Try to start the server
|
|
62
|
+
return self.start(host, port)
|
|
63
|
+
|
|
64
|
+
def start(self, host: str | None = None, port: int | None = None) -> bool:
|
|
65
|
+
"""Start the UI server in a background thread.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
host: Host to bind to (uses config/env if None)
|
|
69
|
+
port: Port to bind to (uses config/env if None)
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if started successfully, False otherwise
|
|
73
|
+
"""
|
|
74
|
+
# Use provided values or get from config
|
|
75
|
+
if host is None or port is None:
|
|
76
|
+
config_host, config_port = get_ui_host_port()
|
|
77
|
+
self._host = host if host is not None else config_host
|
|
78
|
+
self._port = port if port is not None else config_port
|
|
79
|
+
else:
|
|
80
|
+
self._host = host
|
|
81
|
+
self._port = port
|
|
82
|
+
|
|
83
|
+
if self._running:
|
|
84
|
+
return is_ui_running(self._host, self._port)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Check if UI dependencies are available
|
|
88
|
+
try:
|
|
89
|
+
import uvicorn
|
|
90
|
+
|
|
91
|
+
print(f"uvicorn {uvicorn.__version__}")
|
|
92
|
+
except ImportError:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
# Start server in a daemon thread
|
|
96
|
+
def run_server():
|
|
97
|
+
try:
|
|
98
|
+
import uvicorn
|
|
99
|
+
|
|
100
|
+
# Run uvicorn server (blocking call, but in daemon thread)
|
|
101
|
+
uvicorn.run(
|
|
102
|
+
"flowyml.ui.backend.main:app",
|
|
103
|
+
host=host,
|
|
104
|
+
port=port,
|
|
105
|
+
log_level="warning", # Reduce noise in background
|
|
106
|
+
access_log=False,
|
|
107
|
+
)
|
|
108
|
+
except Exception:
|
|
109
|
+
pass # Server will be stopped
|
|
110
|
+
|
|
111
|
+
# Start in daemon thread
|
|
112
|
+
self._server_thread = threading.Thread(
|
|
113
|
+
target=run_server,
|
|
114
|
+
daemon=True,
|
|
115
|
+
name="flowyml-ui-server",
|
|
116
|
+
)
|
|
117
|
+
self._server_thread.start()
|
|
118
|
+
self._running = True
|
|
119
|
+
self._started = True
|
|
120
|
+
|
|
121
|
+
# Wait a bit for server to start
|
|
122
|
+
max_wait = 5
|
|
123
|
+
for _ in range(max_wait * 10): # Check every 100ms
|
|
124
|
+
time.sleep(0.1)
|
|
125
|
+
if is_ui_running(host, port):
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
# If we get here, server didn't start
|
|
129
|
+
self._running = False
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
except Exception:
|
|
133
|
+
self._running = False
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def stop(self) -> None:
|
|
137
|
+
"""Stop the UI server."""
|
|
138
|
+
# Since we're using a daemon thread, it will be killed when main process exits
|
|
139
|
+
# For now, we just mark it as stopped
|
|
140
|
+
self._running = False
|
|
141
|
+
self._server_thread = None
|
|
142
|
+
|
|
143
|
+
def get_url(self) -> Optional[str]:
|
|
144
|
+
"""Get the URL of the running UI server.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
URL string if server is running, None otherwise
|
|
148
|
+
"""
|
|
149
|
+
return get_ui_url(self._host, self._port)
|
|
150
|
+
|
|
151
|
+
def is_running(self) -> bool:
|
|
152
|
+
"""Check if UI server is running."""
|
|
153
|
+
return is_ui_running(self._host, self._port)
|
|
154
|
+
|
|
155
|
+
def get_run_url(self, run_id: str) -> Optional[str]:
|
|
156
|
+
"""Get URL to view a specific pipeline run.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
run_id: ID of the pipeline run
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
URL string if server is running, None otherwise
|
|
163
|
+
"""
|
|
164
|
+
base_url = self.get_url()
|
|
165
|
+
if base_url:
|
|
166
|
+
return f"{base_url}/runs/{run_id}"
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
def get_pipeline_url(self, pipeline_name: str) -> Optional[str]:
|
|
170
|
+
"""Get URL to view a specific pipeline.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
pipeline_name: Name of the pipeline
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
URL string if server is running, None otherwise
|
|
177
|
+
"""
|
|
178
|
+
base_url = self.get_url()
|
|
179
|
+
if base_url:
|
|
180
|
+
return f"{base_url}/pipelines/{pipeline_name}"
|
|
181
|
+
return None
|
flowyml/ui/utils.py
CHANGED
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
"""UI utility functions for checking UI server status and getting URLs."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import http.client
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
def get_ui_server_url() -> str:
|
|
8
|
+
"""Get the UI server URL from configuration or environment variables.
|
|
9
|
+
|
|
10
|
+
Priority order:
|
|
11
|
+
1. FLOWYML_SERVER_URL environment variable (explicit override)
|
|
12
|
+
2. FLOWYML_REMOTE_UI_URL from config (for centralized deployments)
|
|
13
|
+
3. FLOWYML_UI_HOST and FLOWYML_UI_PORT from config/env
|
|
14
|
+
4. Default: http://localhost:8080
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Base URL of the UI server (e.g., "http://localhost:8080" or "https://flowyml.example.com")
|
|
18
|
+
"""
|
|
19
|
+
# Check for explicit server URL override
|
|
20
|
+
server_url = os.getenv("FLOWYML_SERVER_URL")
|
|
21
|
+
if server_url:
|
|
22
|
+
return server_url.rstrip("/")
|
|
23
|
+
|
|
24
|
+
# Check for remote UI URL (centralized deployment)
|
|
25
|
+
try:
|
|
26
|
+
from flowyml.utils.config import get_config
|
|
27
|
+
|
|
28
|
+
config = get_config()
|
|
29
|
+
if config.remote_ui_url:
|
|
30
|
+
return config.remote_ui_url.rstrip("/")
|
|
31
|
+
|
|
32
|
+
# Use config values for host/port
|
|
33
|
+
host = os.getenv("FLOWYML_UI_HOST", config.ui_host)
|
|
34
|
+
port = int(os.getenv("FLOWYML_UI_PORT", str(config.ui_port)))
|
|
35
|
+
|
|
36
|
+
# Determine protocol based on port (443 = https, else http)
|
|
37
|
+
protocol = "https" if port == 443 else "http"
|
|
38
|
+
|
|
39
|
+
return f"{protocol}://{host}:{port}"
|
|
40
|
+
except Exception:
|
|
41
|
+
# Fallback to defaults
|
|
42
|
+
host = os.getenv("FLOWYML_UI_HOST", "localhost")
|
|
43
|
+
port = int(os.getenv("FLOWYML_UI_PORT", "8080"))
|
|
44
|
+
protocol = "https" if port == 443 else "http"
|
|
45
|
+
return f"{protocol}://{host}:{port}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_ui_host_port() -> tuple[str, int]:
|
|
49
|
+
"""Get UI host and port from configuration.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Tuple of (host, port)
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
from flowyml.utils.config import get_config
|
|
56
|
+
|
|
57
|
+
config = get_config()
|
|
58
|
+
host = os.getenv("FLOWYML_UI_HOST", config.ui_host)
|
|
59
|
+
port = int(os.getenv("FLOWYML_UI_PORT", str(config.ui_port)))
|
|
60
|
+
return (host, port)
|
|
61
|
+
except Exception:
|
|
62
|
+
host = os.getenv("FLOWYML_UI_HOST", "localhost")
|
|
63
|
+
port = int(os.getenv("FLOWYML_UI_PORT", "8080"))
|
|
64
|
+
return (host, port)
|
|
65
|
+
|
|
66
|
+
|
|
6
67
|
def is_ui_running(host: str = "localhost", port: int = 8080) -> bool:
|
|
7
68
|
"""Check if the flowyml UI server is running.
|
|
8
69
|
|
|
@@ -39,7 +100,8 @@ def get_ui_url(host: str = "localhost", port: int = 8080) -> str | None:
|
|
|
39
100
|
URL string if server is running, None otherwise
|
|
40
101
|
"""
|
|
41
102
|
if is_ui_running(host, port):
|
|
42
|
-
|
|
103
|
+
protocol = "https" if port == 443 else "http"
|
|
104
|
+
return f"{protocol}://{host}:{port}"
|
|
43
105
|
return None
|
|
44
106
|
|
|
45
107
|
|
flowyml/utils/config.py
CHANGED
|
@@ -27,9 +27,11 @@ class FlowymlConfig:
|
|
|
27
27
|
remote_ui_url: str = ""
|
|
28
28
|
remote_services: list[dict[str, str]] = field(default_factory=list)
|
|
29
29
|
enable_caching: bool = True
|
|
30
|
+
enable_checkpointing: bool = True # Enable checkpointing by default
|
|
30
31
|
enable_logging: bool = True
|
|
31
32
|
log_level: str = "INFO"
|
|
32
33
|
max_cache_size_mb: int = 10000 # 10GB default
|
|
34
|
+
checkpoint_dir: Path = field(default_factory=lambda: Path(".flowyml/checkpoints"))
|
|
33
35
|
|
|
34
36
|
# UI settings
|
|
35
37
|
ui_host: str = "localhost"
|
|
@@ -63,6 +65,7 @@ class FlowymlConfig:
|
|
|
63
65
|
"runs_dir",
|
|
64
66
|
"experiments_dir",
|
|
65
67
|
"projects_dir",
|
|
68
|
+
"checkpoint_dir",
|
|
66
69
|
]:
|
|
67
70
|
value = getattr(self, field_name)
|
|
68
71
|
if not isinstance(value, Path):
|
|
@@ -76,6 +79,7 @@ class FlowymlConfig:
|
|
|
76
79
|
self.runs_dir.mkdir(parents=True, exist_ok=True)
|
|
77
80
|
self.experiments_dir.mkdir(parents=True, exist_ok=True)
|
|
78
81
|
self.projects_dir.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
79
83
|
|
|
80
84
|
# Create metadata db parent dir
|
|
81
85
|
self.metadata_db.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -90,12 +94,14 @@ class FlowymlConfig:
|
|
|
90
94
|
"runs_dir": str(self.runs_dir),
|
|
91
95
|
"experiments_dir": str(self.experiments_dir),
|
|
92
96
|
"projects_dir": str(self.projects_dir),
|
|
97
|
+
"checkpoint_dir": str(self.checkpoint_dir),
|
|
93
98
|
"default_stack": self.default_stack,
|
|
94
99
|
"execution_mode": self.execution_mode,
|
|
95
100
|
"remote_server_url": self.remote_server_url,
|
|
96
101
|
"remote_ui_url": self.remote_ui_url,
|
|
97
102
|
"remote_services": self.remote_services,
|
|
98
103
|
"enable_caching": self.enable_caching,
|
|
104
|
+
"enable_checkpointing": self.enable_checkpointing,
|
|
99
105
|
"enable_logging": self.enable_logging,
|
|
100
106
|
"log_level": self.log_level,
|
|
101
107
|
"max_cache_size_mb": self.max_cache_size_mb,
|
|
@@ -274,6 +280,7 @@ def get_env_config() -> dict[str, Any]:
|
|
|
274
280
|
"flowyml_EXECUTION_MODE": "execution_mode",
|
|
275
281
|
"flowyml_REMOTE_SERVER_URL": "remote_server_url",
|
|
276
282
|
"flowyml_REMOTE_UI_URL": "remote_ui_url",
|
|
283
|
+
"flowyml_SERVER_URL": "remote_ui_url", # Alias for FLOWYML_SERVER_URL -> remote_ui_url
|
|
277
284
|
"flowyml_ENABLE_CACHING": "enable_caching",
|
|
278
285
|
"flowyml_LOG_LEVEL": "log_level",
|
|
279
286
|
"flowyml_UI_HOST": "ui_host",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowyml
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: Next-Generation ML Pipeline Framework
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -23,13 +23,14 @@ Provides-Extra: aws
|
|
|
23
23
|
Provides-Extra: azure
|
|
24
24
|
Provides-Extra: gcp
|
|
25
25
|
Provides-Extra: pytorch
|
|
26
|
+
Provides-Extra: rich
|
|
26
27
|
Provides-Extra: sklearn
|
|
27
28
|
Provides-Extra: tensorflow
|
|
28
29
|
Provides-Extra: ui
|
|
29
30
|
Requires-Dist: click (>=8.0.0)
|
|
30
31
|
Requires-Dist: cloudpickle (>=2.0.0)
|
|
31
32
|
Requires-Dist: croniter (>=2.0.1,<3.0.0)
|
|
32
|
-
Requires-Dist: fastapi (>=0.122.0,<0.123.0) ; extra == "ui"
|
|
33
|
+
Requires-Dist: fastapi (>=0.122.0,<0.123.0) ; extra == "ui" or extra == "all"
|
|
33
34
|
Requires-Dist: google-cloud-aiplatform (>=1.35.0) ; extra == "gcp" or extra == "all"
|
|
34
35
|
Requires-Dist: google-cloud-storage (>=2.10.0) ; extra == "gcp" or extra == "all"
|
|
35
36
|
Requires-Dist: httpx (>=0.24,<0.28)
|
|
@@ -41,6 +42,7 @@ Requires-Dist: pydantic (>=2.0.0)
|
|
|
41
42
|
Requires-Dist: python-multipart (>=0.0.6) ; extra == "ui" or extra == "all"
|
|
42
43
|
Requires-Dist: pytz (>=2024.1,<2025.0)
|
|
43
44
|
Requires-Dist: pyyaml (>=6.0)
|
|
45
|
+
Requires-Dist: rich (>=13.0.0) ; extra == "rich"
|
|
44
46
|
Requires-Dist: scikit-learn (>=1.0.0) ; extra == "sklearn" or extra == "all"
|
|
45
47
|
Requires-Dist: sqlalchemy (>=2.0.0)
|
|
46
48
|
Requires-Dist: tensorflow (>=2.12.0) ; extra == "tensorflow" or extra == "all"
|
|
@@ -254,7 +256,7 @@ pipeline.run(debug=True) # Pauses at breakpoint
|
|
|
254
256
|
Assets are not just files; they are first-class citizens with lineage, metadata, and versioning.
|
|
255
257
|
|
|
256
258
|
```python
|
|
257
|
-
from flowyml
|
|
259
|
+
from flowyml import Dataset, Model, Metrics, FeatureSet
|
|
258
260
|
|
|
259
261
|
# Assets track their producer, lineage, and metadata automatically
|
|
260
262
|
dataset = Dataset.create(data=df, name="training_data", metadata={"source": "s3"})
|