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.
Files changed (104) hide show
  1. flowyml/__init__.py +3 -0
  2. flowyml/assets/base.py +10 -0
  3. flowyml/assets/metrics.py +6 -0
  4. flowyml/cli/main.py +108 -2
  5. flowyml/cli/run.py +9 -2
  6. flowyml/core/execution_status.py +52 -0
  7. flowyml/core/hooks.py +106 -0
  8. flowyml/core/observability.py +210 -0
  9. flowyml/core/orchestrator.py +274 -0
  10. flowyml/core/pipeline.py +193 -231
  11. flowyml/core/project.py +34 -2
  12. flowyml/core/remote_orchestrator.py +109 -0
  13. flowyml/core/resources.py +34 -17
  14. flowyml/core/retry_policy.py +80 -0
  15. flowyml/core/scheduler.py +9 -9
  16. flowyml/core/scheduler_config.py +2 -3
  17. flowyml/core/step.py +18 -1
  18. flowyml/core/submission_result.py +53 -0
  19. flowyml/integrations/keras.py +95 -22
  20. flowyml/monitoring/alerts.py +2 -2
  21. flowyml/stacks/__init__.py +15 -0
  22. flowyml/stacks/aws.py +599 -0
  23. flowyml/stacks/azure.py +295 -0
  24. flowyml/stacks/bridge.py +9 -9
  25. flowyml/stacks/components.py +24 -2
  26. flowyml/stacks/gcp.py +158 -11
  27. flowyml/stacks/local.py +5 -0
  28. flowyml/stacks/plugins.py +2 -2
  29. flowyml/stacks/registry.py +21 -0
  30. flowyml/storage/artifacts.py +15 -5
  31. flowyml/storage/materializers/__init__.py +2 -0
  32. flowyml/storage/materializers/base.py +33 -0
  33. flowyml/storage/materializers/cloudpickle.py +74 -0
  34. flowyml/storage/metadata.py +3 -881
  35. flowyml/storage/remote.py +590 -0
  36. flowyml/storage/sql.py +911 -0
  37. flowyml/ui/backend/dependencies.py +28 -0
  38. flowyml/ui/backend/main.py +43 -80
  39. flowyml/ui/backend/routers/assets.py +483 -17
  40. flowyml/ui/backend/routers/client.py +46 -0
  41. flowyml/ui/backend/routers/execution.py +13 -2
  42. flowyml/ui/backend/routers/experiments.py +97 -14
  43. flowyml/ui/backend/routers/metrics.py +168 -0
  44. flowyml/ui/backend/routers/pipelines.py +77 -12
  45. flowyml/ui/backend/routers/projects.py +33 -7
  46. flowyml/ui/backend/routers/runs.py +221 -12
  47. flowyml/ui/backend/routers/schedules.py +5 -21
  48. flowyml/ui/backend/routers/stats.py +14 -0
  49. flowyml/ui/backend/routers/traces.py +37 -53
  50. flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +1 -0
  51. flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +592 -0
  52. flowyml/ui/frontend/dist/index.html +2 -2
  53. flowyml/ui/frontend/src/App.jsx +4 -1
  54. flowyml/ui/frontend/src/app/assets/page.jsx +260 -230
  55. flowyml/ui/frontend/src/app/dashboard/page.jsx +38 -7
  56. flowyml/ui/frontend/src/app/experiments/page.jsx +61 -314
  57. flowyml/ui/frontend/src/app/observability/page.jsx +277 -0
  58. flowyml/ui/frontend/src/app/pipelines/page.jsx +79 -402
  59. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectArtifactsList.jsx +151 -0
  60. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +145 -0
  61. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHeader.jsx +45 -0
  62. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHierarchy.jsx +467 -0
  63. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +253 -0
  64. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectPipelinesList.jsx +105 -0
  65. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRelations.jsx +189 -0
  66. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +136 -0
  67. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectTabs.jsx +95 -0
  68. flowyml/ui/frontend/src/app/projects/[projectId]/page.jsx +326 -0
  69. flowyml/ui/frontend/src/app/projects/page.jsx +13 -3
  70. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +79 -10
  71. flowyml/ui/frontend/src/app/runs/page.jsx +82 -424
  72. flowyml/ui/frontend/src/app/settings/page.jsx +1 -0
  73. flowyml/ui/frontend/src/app/tokens/page.jsx +62 -16
  74. flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +373 -0
  75. flowyml/ui/frontend/src/components/AssetLineageGraph.jsx +291 -0
  76. flowyml/ui/frontend/src/components/AssetStatsDashboard.jsx +302 -0
  77. flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +477 -0
  78. flowyml/ui/frontend/src/components/ExperimentDetailsPanel.jsx +227 -0
  79. flowyml/ui/frontend/src/components/NavigationTree.jsx +401 -0
  80. flowyml/ui/frontend/src/components/PipelineDetailsPanel.jsx +239 -0
  81. flowyml/ui/frontend/src/components/PipelineGraph.jsx +67 -3
  82. flowyml/ui/frontend/src/components/ProjectSelector.jsx +115 -0
  83. flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +298 -0
  84. flowyml/ui/frontend/src/components/header/Header.jsx +48 -1
  85. flowyml/ui/frontend/src/components/plugins/ZenMLIntegration.jsx +106 -0
  86. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +52 -26
  87. flowyml/ui/frontend/src/components/ui/DataView.jsx +35 -17
  88. flowyml/ui/frontend/src/components/ui/ErrorBoundary.jsx +118 -0
  89. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +2 -2
  90. flowyml/ui/frontend/src/contexts/ToastContext.jsx +116 -0
  91. flowyml/ui/frontend/src/layouts/MainLayout.jsx +5 -1
  92. flowyml/ui/frontend/src/router/index.jsx +4 -0
  93. flowyml/ui/frontend/src/utils/date.js +10 -0
  94. flowyml/ui/frontend/src/utils/downloads.js +11 -0
  95. flowyml/utils/config.py +6 -0
  96. flowyml/utils/stack_config.py +45 -3
  97. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/METADATA +44 -4
  98. flowyml-1.4.0.dist-info/RECORD +200 -0
  99. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/licenses/LICENSE +1 -1
  100. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +0 -448
  101. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +0 -1
  102. flowyml-1.2.0.dist-info/RECORD +0 -159
  103. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/WHEEL +0 -0
  104. {flowyml-1.2.0.dist-info → flowyml-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -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
 
@@ -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 pickle
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
- with open(full_path / "data.pkl", "wb") as f:
241
- pickle.dump(obj, f)
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({"type": "pickle", "format": "pickle"}, f, indent=2)
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 []