flowyml 1.2.0__py3-none-any.whl → 1.4.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 +3 -0
- flowyml/assets/base.py +10 -0
- flowyml/assets/metrics.py +6 -0
- flowyml/cli/main.py +108 -2
- flowyml/cli/run.py +9 -2
- flowyml/core/execution_status.py +52 -0
- flowyml/core/hooks.py +106 -0
- flowyml/core/observability.py +210 -0
- flowyml/core/orchestrator.py +274 -0
- flowyml/core/pipeline.py +193 -231
- flowyml/core/project.py +34 -2
- flowyml/core/remote_orchestrator.py +109 -0
- flowyml/core/resources.py +34 -17
- flowyml/core/retry_policy.py +80 -0
- flowyml/core/scheduler.py +9 -9
- flowyml/core/scheduler_config.py +2 -3
- flowyml/core/step.py +18 -1
- flowyml/core/submission_result.py +53 -0
- flowyml/integrations/keras.py +95 -22
- flowyml/monitoring/alerts.py +2 -2
- flowyml/stacks/__init__.py +15 -0
- flowyml/stacks/aws.py +599 -0
- flowyml/stacks/azure.py +295 -0
- flowyml/stacks/bridge.py +9 -9
- flowyml/stacks/components.py +24 -2
- flowyml/stacks/gcp.py +158 -11
- flowyml/stacks/local.py +5 -0
- flowyml/stacks/plugins.py +2 -2
- flowyml/stacks/registry.py +21 -0
- flowyml/storage/artifacts.py +15 -5
- flowyml/storage/materializers/__init__.py +2 -0
- flowyml/storage/materializers/base.py +33 -0
- flowyml/storage/materializers/cloudpickle.py +74 -0
- flowyml/storage/metadata.py +3 -881
- flowyml/storage/remote.py +590 -0
- flowyml/storage/sql.py +911 -0
- flowyml/ui/backend/dependencies.py +28 -0
- flowyml/ui/backend/main.py +43 -80
- flowyml/ui/backend/routers/assets.py +483 -17
- flowyml/ui/backend/routers/client.py +46 -0
- flowyml/ui/backend/routers/execution.py +13 -2
- flowyml/ui/backend/routers/experiments.py +97 -14
- flowyml/ui/backend/routers/metrics.py +168 -0
- flowyml/ui/backend/routers/pipelines.py +77 -12
- flowyml/ui/backend/routers/projects.py +33 -7
- flowyml/ui/backend/routers/runs.py +221 -12
- flowyml/ui/backend/routers/schedules.py +5 -21
- flowyml/ui/backend/routers/stats.py +14 -0
- flowyml/ui/backend/routers/traces.py +37 -53
- flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +1 -0
- flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +592 -0
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/src/App.jsx +4 -1
- flowyml/ui/frontend/src/app/assets/page.jsx +260 -230
- flowyml/ui/frontend/src/app/dashboard/page.jsx +38 -7
- flowyml/ui/frontend/src/app/experiments/page.jsx +61 -314
- flowyml/ui/frontend/src/app/observability/page.jsx +277 -0
- flowyml/ui/frontend/src/app/pipelines/page.jsx +79 -402
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectArtifactsList.jsx +151 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +145 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHeader.jsx +45 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHierarchy.jsx +467 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +253 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectPipelinesList.jsx +105 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRelations.jsx +189 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +136 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectTabs.jsx +95 -0
- flowyml/ui/frontend/src/app/projects/[projectId]/page.jsx +326 -0
- flowyml/ui/frontend/src/app/projects/page.jsx +13 -3
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +79 -10
- flowyml/ui/frontend/src/app/runs/page.jsx +82 -424
- flowyml/ui/frontend/src/app/settings/page.jsx +1 -0
- flowyml/ui/frontend/src/app/tokens/page.jsx +62 -16
- flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +373 -0
- flowyml/ui/frontend/src/components/AssetLineageGraph.jsx +291 -0
- flowyml/ui/frontend/src/components/AssetStatsDashboard.jsx +302 -0
- flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +477 -0
- flowyml/ui/frontend/src/components/ExperimentDetailsPanel.jsx +227 -0
- flowyml/ui/frontend/src/components/NavigationTree.jsx +401 -0
- flowyml/ui/frontend/src/components/PipelineDetailsPanel.jsx +239 -0
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +67 -3
- flowyml/ui/frontend/src/components/ProjectSelector.jsx +115 -0
- flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +298 -0
- flowyml/ui/frontend/src/components/header/Header.jsx +48 -1
- flowyml/ui/frontend/src/components/plugins/ZenMLIntegration.jsx +106 -0
- flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +52 -26
- flowyml/ui/frontend/src/components/ui/DataView.jsx +35 -17
- flowyml/ui/frontend/src/components/ui/ErrorBoundary.jsx +118 -0
- flowyml/ui/frontend/src/contexts/ProjectContext.jsx +2 -2
- flowyml/ui/frontend/src/contexts/ToastContext.jsx +116 -0
- flowyml/ui/frontend/src/layouts/MainLayout.jsx +5 -1
- flowyml/ui/frontend/src/router/index.jsx +4 -0
- flowyml/ui/frontend/src/utils/date.js +10 -0
- flowyml/ui/frontend/src/utils/downloads.js +11 -0
- flowyml/utils/config.py +6 -0
- flowyml/utils/stack_config.py +45 -3
- {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/METADATA +44 -4
- flowyml-1.4.0.dist-info/RECORD +200 -0
- {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/licenses/LICENSE +1 -1
- flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +0 -448
- flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +0 -1
- flowyml-1.2.0.dist-info/RECORD +0 -159
- {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/WHEEL +0 -0
- {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/entry_points.txt +0 -0
flowyml/stacks/registry.py
CHANGED
|
@@ -214,6 +214,27 @@ def get_active_stack() -> Stack | None:
|
|
|
214
214
|
Returns:
|
|
215
215
|
Active stack or None
|
|
216
216
|
"""
|
|
217
|
+
# Check for remote execution mode in global config
|
|
218
|
+
from flowyml.utils.config import get_config
|
|
219
|
+
|
|
220
|
+
config = get_config()
|
|
221
|
+
|
|
222
|
+
if config.execution_mode == "remote" and config.remote_server_url:
|
|
223
|
+
# Dynamically create a remote logging stack
|
|
224
|
+
from flowyml.core.executor import LocalExecutor
|
|
225
|
+
from flowyml.storage.remote import RemoteMetadataStore, RemoteArtifactStore
|
|
226
|
+
from flowyml.stacks.base import Stack
|
|
227
|
+
import os
|
|
228
|
+
|
|
229
|
+
api_token = os.getenv("FLOWYML_API_TOKEN")
|
|
230
|
+
|
|
231
|
+
return Stack(
|
|
232
|
+
name="remote_logging",
|
|
233
|
+
executor=LocalExecutor(),
|
|
234
|
+
metadata_store=RemoteMetadataStore(api_url=config.remote_server_url, api_token=api_token),
|
|
235
|
+
artifact_store=RemoteArtifactStore(api_url=config.remote_server_url, api_token=api_token),
|
|
236
|
+
)
|
|
237
|
+
|
|
217
238
|
return get_registry().get_active_stack()
|
|
218
239
|
|
|
219
240
|
|
flowyml/storage/artifacts.py
CHANGED
|
@@ -215,7 +215,7 @@ class LocalArtifactStore(ArtifactStore):
|
|
|
215
215
|
from datetime import datetime
|
|
216
216
|
from flowyml.storage.materializers.base import get_materializer
|
|
217
217
|
import shutil
|
|
218
|
-
import
|
|
218
|
+
import cloudpickle
|
|
219
219
|
import json
|
|
220
220
|
|
|
221
221
|
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
@@ -236,11 +236,21 @@ class LocalArtifactStore(ArtifactStore):
|
|
|
236
236
|
if materializer:
|
|
237
237
|
materializer.save(obj, full_path)
|
|
238
238
|
else:
|
|
239
|
-
# Fallback to pickle
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
# Fallback to cloudpickle (more robust than pickle)
|
|
240
|
+
fallback_file = full_path / "data.pkl"
|
|
241
|
+
with open(fallback_file, "wb") as f:
|
|
242
|
+
cloudpickle.dump(obj, f)
|
|
242
243
|
# Save metadata
|
|
243
244
|
with open(full_path / "metadata.json", "w") as f:
|
|
244
|
-
json.dump(
|
|
245
|
+
json.dump(
|
|
246
|
+
{
|
|
247
|
+
"type": type(obj).__name__,
|
|
248
|
+
"serializer": "cloudpickle",
|
|
249
|
+
"format": "pickle",
|
|
250
|
+
"file": fallback_file.name,
|
|
251
|
+
},
|
|
252
|
+
f,
|
|
253
|
+
indent=2,
|
|
254
|
+
)
|
|
245
255
|
|
|
246
256
|
return str(full_path)
|
|
@@ -7,6 +7,7 @@ from flowyml.storage.materializers.sklearn import SklearnMaterializer
|
|
|
7
7
|
from flowyml.storage.materializers.pandas import PandasMaterializer
|
|
8
8
|
from flowyml.storage.materializers.numpy import NumPyMaterializer
|
|
9
9
|
from flowyml.storage.materializers.keras import KerasMaterializer
|
|
10
|
+
from flowyml.storage.materializers.cloudpickle import CloudpickleMaterializer
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
12
13
|
"BaseMaterializer",
|
|
@@ -17,4 +18,5 @@ __all__ = [
|
|
|
17
18
|
"PandasMaterializer",
|
|
18
19
|
"NumPyMaterializer",
|
|
19
20
|
"KerasMaterializer",
|
|
21
|
+
"CloudpickleMaterializer",
|
|
20
22
|
]
|
|
@@ -99,6 +99,27 @@ class MaterializerRegistry:
|
|
|
99
99
|
|
|
100
100
|
return None
|
|
101
101
|
|
|
102
|
+
def get_materializer_by_type_name(self, type_name: str) -> BaseMaterializer | None:
|
|
103
|
+
"""Get materializer by type name string.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
type_name: Full type name (e.g. 'pandas.core.frame.DataFrame')
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Materializer instance or None
|
|
110
|
+
"""
|
|
111
|
+
# Check if we have a direct mapping
|
|
112
|
+
if type_name in self._materializers:
|
|
113
|
+
return self._materializers[type_name]()
|
|
114
|
+
|
|
115
|
+
# Try to find by just class name if full path fails (less reliable but useful fallback)
|
|
116
|
+
simple_name = type_name.split(".")[-1]
|
|
117
|
+
for key, materializer_cls in self._materializers.items():
|
|
118
|
+
if key.split(".")[-1] == simple_name:
|
|
119
|
+
return materializer_cls()
|
|
120
|
+
|
|
121
|
+
return None
|
|
122
|
+
|
|
102
123
|
def list_materializers(self) -> dict[str, type[BaseMaterializer]]:
|
|
103
124
|
"""List all registered materializers.
|
|
104
125
|
|
|
@@ -131,3 +152,15 @@ def get_materializer(obj: Any) -> BaseMaterializer | None:
|
|
|
131
152
|
Materializer instance or None
|
|
132
153
|
"""
|
|
133
154
|
return materializer_registry.get_materializer(obj)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_materializer_by_type_name(type_name: str) -> BaseMaterializer | None:
|
|
158
|
+
"""Get materializer by type name string from global registry.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
type_name: Full type name (e.g. 'pandas.core.frame.DataFrame')
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Materializer instance or None
|
|
165
|
+
"""
|
|
166
|
+
return materializer_registry.get_materializer_by_type_name(type_name)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Cloudpickle materializer for robust Python object serialization."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from flowyml.storage.materializers.base import BaseMaterializer
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import cloudpickle
|
|
10
|
+
|
|
11
|
+
CLOUDPICKLE_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
CLOUDPICKLE_AVAILABLE = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if CLOUDPICKLE_AVAILABLE:
|
|
17
|
+
|
|
18
|
+
class CloudpickleMaterializer(BaseMaterializer):
|
|
19
|
+
"""Materializer using cloudpickle for robust serialization."""
|
|
20
|
+
|
|
21
|
+
def save(self, obj: Any, path: Path) -> None:
|
|
22
|
+
"""Save object using cloudpickle.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
obj: Object to save
|
|
26
|
+
path: Directory path where object should be saved
|
|
27
|
+
"""
|
|
28
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
file_path = path / "data.cloudpickle"
|
|
31
|
+
with open(file_path, "wb") as f:
|
|
32
|
+
cloudpickle.dump(obj, f)
|
|
33
|
+
|
|
34
|
+
def load(self, path: Path) -> Any:
|
|
35
|
+
"""Load object using cloudpickle.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
path: Directory path from which to load object
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Loaded object
|
|
42
|
+
"""
|
|
43
|
+
file_path = path / "data.cloudpickle"
|
|
44
|
+
if not file_path.exists():
|
|
45
|
+
raise FileNotFoundError(f"Cloudpickle file not found at {file_path}")
|
|
46
|
+
|
|
47
|
+
with open(file_path, "rb") as f:
|
|
48
|
+
return cloudpickle.load(f)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def supported_types(cls) -> list[type]:
|
|
52
|
+
"""Return types supported by this materializer.
|
|
53
|
+
|
|
54
|
+
Cloudpickle can handle almost anything, so we expose ``object`` here
|
|
55
|
+
for consumers that explicitly opt into this materializer as a
|
|
56
|
+
fallback, but it is not registered automatically with the global
|
|
57
|
+
registry to avoid hijacking unknown types.
|
|
58
|
+
"""
|
|
59
|
+
return [object]
|
|
60
|
+
|
|
61
|
+
else:
|
|
62
|
+
|
|
63
|
+
class CloudpickleMaterializer(BaseMaterializer):
|
|
64
|
+
"""Placeholder when cloudpickle is not available."""
|
|
65
|
+
|
|
66
|
+
def save(self, obj: Any, path: Path) -> None:
|
|
67
|
+
raise ImportError("cloudpickle is not installed")
|
|
68
|
+
|
|
69
|
+
def load(self, path: Path) -> Any:
|
|
70
|
+
raise ImportError("cloudpickle is not installed")
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def supported_types(cls) -> list[type]:
|
|
74
|
+
return []
|