flowyml 1.2.0__py3-none-any.whl → 1.3.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 (91) 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 +22 -5
  14. flowyml/core/retry_policy.py +80 -0
  15. flowyml/core/step.py +18 -1
  16. flowyml/core/submission_result.py +53 -0
  17. flowyml/integrations/keras.py +95 -22
  18. flowyml/monitoring/alerts.py +2 -2
  19. flowyml/stacks/__init__.py +15 -0
  20. flowyml/stacks/aws.py +599 -0
  21. flowyml/stacks/azure.py +295 -0
  22. flowyml/stacks/components.py +24 -2
  23. flowyml/stacks/gcp.py +158 -11
  24. flowyml/stacks/local.py +5 -0
  25. flowyml/storage/artifacts.py +15 -5
  26. flowyml/storage/materializers/__init__.py +2 -0
  27. flowyml/storage/materializers/cloudpickle.py +74 -0
  28. flowyml/storage/metadata.py +166 -5
  29. flowyml/ui/backend/main.py +41 -1
  30. flowyml/ui/backend/routers/assets.py +356 -15
  31. flowyml/ui/backend/routers/client.py +46 -0
  32. flowyml/ui/backend/routers/execution.py +13 -2
  33. flowyml/ui/backend/routers/experiments.py +48 -12
  34. flowyml/ui/backend/routers/metrics.py +213 -0
  35. flowyml/ui/backend/routers/pipelines.py +63 -7
  36. flowyml/ui/backend/routers/projects.py +33 -7
  37. flowyml/ui/backend/routers/runs.py +150 -8
  38. flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +1 -0
  39. flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +592 -0
  40. flowyml/ui/frontend/dist/index.html +2 -2
  41. flowyml/ui/frontend/src/App.jsx +4 -1
  42. flowyml/ui/frontend/src/app/assets/page.jsx +260 -230
  43. flowyml/ui/frontend/src/app/dashboard/page.jsx +38 -7
  44. flowyml/ui/frontend/src/app/experiments/page.jsx +61 -314
  45. flowyml/ui/frontend/src/app/observability/page.jsx +277 -0
  46. flowyml/ui/frontend/src/app/pipelines/page.jsx +79 -402
  47. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectArtifactsList.jsx +151 -0
  48. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +145 -0
  49. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHeader.jsx +45 -0
  50. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHierarchy.jsx +467 -0
  51. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +253 -0
  52. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectPipelinesList.jsx +105 -0
  53. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRelations.jsx +189 -0
  54. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +136 -0
  55. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectTabs.jsx +95 -0
  56. flowyml/ui/frontend/src/app/projects/[projectId]/page.jsx +326 -0
  57. flowyml/ui/frontend/src/app/projects/page.jsx +13 -3
  58. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +79 -10
  59. flowyml/ui/frontend/src/app/runs/page.jsx +82 -424
  60. flowyml/ui/frontend/src/app/settings/page.jsx +1 -0
  61. flowyml/ui/frontend/src/app/tokens/page.jsx +62 -16
  62. flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +373 -0
  63. flowyml/ui/frontend/src/components/AssetLineageGraph.jsx +291 -0
  64. flowyml/ui/frontend/src/components/AssetStatsDashboard.jsx +302 -0
  65. flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +477 -0
  66. flowyml/ui/frontend/src/components/ExperimentDetailsPanel.jsx +227 -0
  67. flowyml/ui/frontend/src/components/NavigationTree.jsx +401 -0
  68. flowyml/ui/frontend/src/components/PipelineDetailsPanel.jsx +239 -0
  69. flowyml/ui/frontend/src/components/PipelineGraph.jsx +67 -3
  70. flowyml/ui/frontend/src/components/ProjectSelector.jsx +115 -0
  71. flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +298 -0
  72. flowyml/ui/frontend/src/components/header/Header.jsx +48 -1
  73. flowyml/ui/frontend/src/components/plugins/ZenMLIntegration.jsx +106 -0
  74. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +52 -26
  75. flowyml/ui/frontend/src/components/ui/DataView.jsx +35 -17
  76. flowyml/ui/frontend/src/components/ui/ErrorBoundary.jsx +118 -0
  77. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +2 -2
  78. flowyml/ui/frontend/src/contexts/ToastContext.jsx +116 -0
  79. flowyml/ui/frontend/src/layouts/MainLayout.jsx +5 -1
  80. flowyml/ui/frontend/src/router/index.jsx +4 -0
  81. flowyml/ui/frontend/src/utils/date.js +10 -0
  82. flowyml/ui/frontend/src/utils/downloads.js +11 -0
  83. flowyml/utils/config.py +6 -0
  84. flowyml/utils/stack_config.py +45 -3
  85. {flowyml-1.2.0.dist-info → flowyml-1.3.0.dist-info}/METADATA +42 -4
  86. {flowyml-1.2.0.dist-info → flowyml-1.3.0.dist-info}/RECORD +89 -52
  87. {flowyml-1.2.0.dist-info → flowyml-1.3.0.dist-info}/licenses/LICENSE +1 -1
  88. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +0 -448
  89. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +0 -1
  90. {flowyml-1.2.0.dist-info → flowyml-1.3.0.dist-info}/WHEEL +0 -0
  91. {flowyml-1.2.0.dist-info → flowyml-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,151 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { fetchApi } from '../../../../utils/api';
3
+ import { DataView } from '../../../../components/ui/DataView';
4
+ import { FileBox, Database, Box, Clock, Download } from 'lucide-react';
5
+ import { formatDate } from '../../../../utils/date';
6
+ import { downloadArtifactById } from '../../../../utils/downloads';
7
+
8
+ export function ProjectArtifactsList({ projectId, type }) {
9
+ const [artifacts, setArtifacts] = useState([]);
10
+ const [loading, setLoading] = useState(true);
11
+
12
+ useEffect(() => {
13
+ const fetchArtifacts = async () => {
14
+ try {
15
+ let url = `/api/assets?project=${projectId}`;
16
+ if (type) {
17
+ url += `&asset_type=${type}`;
18
+ }
19
+
20
+ const response = await fetchApi(url);
21
+ const data = await response.json();
22
+
23
+ if (data.assets && Array.isArray(data.assets)) {
24
+ setArtifacts(data.assets);
25
+ } else {
26
+ setArtifacts([]);
27
+ }
28
+ } catch (error) {
29
+ console.error('Failed to fetch artifacts:', error);
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ };
34
+
35
+ if (projectId) {
36
+ fetchArtifacts();
37
+ }
38
+ }, [projectId, type]);
39
+
40
+ const columns = [
41
+ {
42
+ key: 'name',
43
+ label: 'Name',
44
+ sortable: true,
45
+ render: (item) => (
46
+ <div className="flex items-center gap-2">
47
+ <FileBox className="w-4 h-4 text-blue-500" />
48
+ <span className="font-medium text-slate-900 dark:text-white">{item.name}</span>
49
+ </div>
50
+ )
51
+ },
52
+ {
53
+ key: 'type',
54
+ label: 'Type',
55
+ sortable: true,
56
+ render: (item) => (
57
+ <span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-200">
58
+ {item.type}
59
+ </span>
60
+ )
61
+ },
62
+ {
63
+ key: 'created_at',
64
+ label: 'Created',
65
+ sortable: true,
66
+ render: (item) => (
67
+ <div className="flex items-center gap-1 text-slate-500">
68
+ <Clock className="w-3 h-3" />
69
+ <span>{formatDate(item.created_at)}</span>
70
+ </div>
71
+ )
72
+ },
73
+ {
74
+ key: 'path',
75
+ label: 'Path',
76
+ render: (item) => (
77
+ <span className="text-xs text-slate-400 font-mono truncate max-w-[200px] block" title={item.path}>
78
+ {item.path}
79
+ </span>
80
+ )
81
+ },
82
+ {
83
+ key: 'actions',
84
+ label: 'Actions',
85
+ render: (item) => (
86
+ <button
87
+ onClick={() => downloadArtifactById(item.artifact_id)}
88
+ className="inline-flex items-center gap-1 px-3 py-1.5 rounded-lg text-sm font-medium text-primary-600 hover:text-primary-700 hover:bg-primary-50 transition-colors disabled:opacity-50"
89
+ disabled={!item.artifact_id}
90
+ >
91
+ <Download size={14} /> Download
92
+ </button>
93
+ )
94
+ }
95
+ ];
96
+
97
+ return (
98
+ <DataView
99
+ items={artifacts}
100
+ loading={loading}
101
+ columns={columns}
102
+ initialView="table"
103
+ searchKeys={['name', 'type', 'path']}
104
+ renderGrid={(item) => (
105
+ <div className="p-4 bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 hover:shadow-md transition-shadow">
106
+ <div className="flex items-start justify-between mb-3">
107
+ <div className="flex items-center gap-2">
108
+ <div className="p-2 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
109
+ <FileBox className="w-5 h-5 text-blue-500" />
110
+ </div>
111
+ <div>
112
+ <h3 className="font-medium text-slate-900 dark:text-white">{item.name}</h3>
113
+ <span className="text-xs text-slate-500">{item.type}</span>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <div className="space-y-2 text-sm text-slate-600 dark:text-slate-400">
119
+ <div className="flex items-center gap-2">
120
+ <Clock className="w-4 h-4" />
121
+ <span>{formatDate(item.created_at)}</span>
122
+ </div>
123
+ <div className="flex items-center gap-2">
124
+ <Database className="w-4 h-4" />
125
+ <span className="truncate font-mono text-xs">{item.path}</span>
126
+ </div>
127
+ </div>
128
+
129
+ <button
130
+ onClick={() => downloadArtifactById(item.artifact_id)}
131
+ className="mt-4 inline-flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-primary-50 text-primary-700 hover:bg-primary-100 transition-colors disabled:opacity-50"
132
+ disabled={!item.artifact_id}
133
+ >
134
+ <Download size={16} /> Download
135
+ </button>
136
+ </div>
137
+ )}
138
+ emptyState={
139
+ <div className="text-center py-12">
140
+ <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-slate-100 dark:bg-slate-800 mb-4">
141
+ <Box className="w-6 h-6 text-slate-400" />
142
+ </div>
143
+ <h3 className="text-lg font-medium text-slate-900 dark:text-white mb-1">No artifacts found</h3>
144
+ <p className="text-slate-500">
145
+ {type ? `No ${type}s have been created yet.` : 'No artifacts have been created yet.'}
146
+ </p>
147
+ </div>
148
+ }
149
+ />
150
+ );
151
+ }
@@ -0,0 +1,145 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { fetchApi } from '../../../../utils/api';
3
+ import { DataView } from '../../../../components/ui/DataView';
4
+ import { FlaskConical, Clock, Tag, PlayCircle } from 'lucide-react';
5
+ import { formatDate } from '../../../../utils/date';
6
+
7
+ export function ProjectExperimentsList({ projectId }) {
8
+ const [experiments, setExperiments] = useState([]);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ useEffect(() => {
12
+ const fetchExperiments = async () => {
13
+ try {
14
+ const response = await fetchApi(`/api/experiments?project=${projectId}`);
15
+ const data = await response.json();
16
+
17
+ if (data.experiments && Array.isArray(data.experiments)) {
18
+ setExperiments(data.experiments);
19
+ } else {
20
+ setExperiments([]);
21
+ }
22
+ } catch (error) {
23
+ console.error('Failed to fetch experiments:', error);
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+
29
+ if (projectId) {
30
+ fetchExperiments();
31
+ }
32
+ }, [projectId]);
33
+
34
+ const columns = [
35
+ {
36
+ key: 'name',
37
+ label: 'Name',
38
+ sortable: true,
39
+ render: (item) => (
40
+ <div className="flex items-center gap-2">
41
+ <FlaskConical className="w-4 h-4 text-purple-500" />
42
+ <div>
43
+ <div className="font-medium text-slate-900 dark:text-white">{item.name}</div>
44
+ {item.description && (
45
+ <div className="text-xs text-slate-500 truncate max-w-[200px]">{item.description}</div>
46
+ )}
47
+ </div>
48
+ </div>
49
+ )
50
+ },
51
+ {
52
+ key: 'run_count',
53
+ label: 'Runs',
54
+ sortable: true,
55
+ render: (item) => (
56
+ <div className="flex items-center gap-1">
57
+ <PlayCircle className="w-3 h-3 text-slate-400" />
58
+ <span>{item.run_count || 0}</span>
59
+ </div>
60
+ )
61
+ },
62
+ {
63
+ key: 'created_at',
64
+ label: 'Created',
65
+ sortable: true,
66
+ render: (item) => (
67
+ <div className="flex items-center gap-1 text-slate-500">
68
+ <Clock className="w-3 h-3" />
69
+ <span>{formatDate(item.created_at)}</span>
70
+ </div>
71
+ )
72
+ },
73
+ {
74
+ key: 'tags',
75
+ label: 'Tags',
76
+ render: (item) => (
77
+ <div className="flex flex-wrap gap-1">
78
+ {Object.entries(item.tags || {}).map(([key, value]) => (
79
+ <span key={key} className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400">
80
+ <Tag className="w-3 h-3 mr-1" />
81
+ {key}: {value}
82
+ </span>
83
+ ))}
84
+ </div>
85
+ )
86
+ }
87
+ ];
88
+
89
+ return (
90
+ <DataView
91
+ items={experiments}
92
+ loading={loading}
93
+ columns={columns}
94
+ initialView="table"
95
+ searchKeys={['name', 'description']}
96
+ renderGrid={(item) => (
97
+ <div className="p-4 bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 hover:shadow-md transition-shadow">
98
+ <div className="flex items-start justify-between mb-3">
99
+ <div className="flex items-center gap-2">
100
+ <div className="p-2 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
101
+ <FlaskConical className="w-5 h-5 text-purple-500" />
102
+ </div>
103
+ <div>
104
+ <h3 className="font-medium text-slate-900 dark:text-white">{item.name}</h3>
105
+ <div className="text-xs text-slate-500 flex items-center gap-1">
106
+ <Clock className="w-3 h-3" />
107
+ {formatDate(item.created_at)}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ <div className="flex items-center gap-1 text-xs font-medium bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded-full">
112
+ <PlayCircle className="w-3 h-3" />
113
+ {item.run_count || 0} runs
114
+ </div>
115
+ </div>
116
+
117
+ {item.description && (
118
+ <p className="text-sm text-slate-600 dark:text-slate-400 mb-3 line-clamp-2">
119
+ {item.description}
120
+ </p>
121
+ )}
122
+
123
+ <div className="flex flex-wrap gap-1">
124
+ {Object.entries(item.tags || {}).map(([key, value]) => (
125
+ <span key={key} className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400">
126
+ {key}: {value}
127
+ </span>
128
+ ))}
129
+ </div>
130
+ </div>
131
+ )}
132
+ emptyState={
133
+ <div className="text-center py-12">
134
+ <div className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-slate-100 dark:bg-slate-800 mb-4">
135
+ <FlaskConical className="w-6 h-6 text-slate-400" />
136
+ </div>
137
+ <h3 className="text-lg font-medium text-slate-900 dark:text-white mb-1">No experiments found</h3>
138
+ <p className="text-slate-500">
139
+ Create an experiment to track your model development.
140
+ </p>
141
+ </div>
142
+ }
143
+ />
144
+ );
145
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { Activity, Clock, Database, Folder, GitBranch, Box } from 'lucide-react';
3
+ import { formatDate } from '../../../../utils/date';
4
+
5
+ export function ProjectHeader({ project, stats, loading }) {
6
+ if (loading || !project) {
7
+ return (
8
+ <div className="animate-pulse space-y-4">
9
+ <div className="h-8 bg-slate-200 dark:bg-slate-700 rounded w-1/3"></div>
10
+ <div className="h-4 bg-slate-200 dark:bg-slate-700 rounded w-1/2"></div>
11
+ <div className="grid grid-cols-4 gap-4">
12
+ <div className="h-24 bg-slate-200 dark:bg-slate-700 rounded-xl"></div>
13
+ <div className="h-24 bg-slate-200 dark:bg-slate-700 rounded-xl"></div>
14
+ <div className="h-24 bg-slate-200 dark:bg-slate-700 rounded-xl"></div>
15
+ <div className="h-24 bg-slate-200 dark:bg-slate-700 rounded-xl"></div>
16
+ </div>
17
+ </div>
18
+ );
19
+ }
20
+
21
+ return (
22
+ <div className="flex items-start justify-between">
23
+ <div className="flex items-center gap-4">
24
+ <div className="p-3 bg-blue-600 rounded-xl shadow-lg shadow-blue-500/20">
25
+ <Folder className="w-6 h-6 text-white" />
26
+ </div>
27
+ <div>
28
+ <h1 className="text-2xl font-bold text-slate-900 dark:text-white mb-1">{project.name}</h1>
29
+ <div className="flex items-center gap-4 text-sm text-slate-500 dark:text-slate-400">
30
+ <span>Created {formatDate(project.metadata?.created_at || project.created_at)}</span>
31
+ {(project.metadata?.description || project.description) && (
32
+ <>
33
+ <span>•</span>
34
+ <span>{project.metadata?.description || project.description}</span>
35
+ </>
36
+ )}
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <div className="flex gap-2">
41
+ {/* Actions like Edit/Delete can go here */}
42
+ </div>
43
+ </div>
44
+ );
45
+ }