flowyml 1.1.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 +207 -0
- flowyml/assets/__init__.py +22 -0
- flowyml/assets/artifact.py +40 -0
- flowyml/assets/base.py +209 -0
- flowyml/assets/dataset.py +100 -0
- flowyml/assets/featureset.py +301 -0
- flowyml/assets/metrics.py +104 -0
- flowyml/assets/model.py +82 -0
- flowyml/assets/registry.py +157 -0
- flowyml/assets/report.py +315 -0
- flowyml/cli/__init__.py +5 -0
- flowyml/cli/experiment.py +232 -0
- flowyml/cli/init.py +256 -0
- flowyml/cli/main.py +327 -0
- flowyml/cli/run.py +75 -0
- flowyml/cli/stack_cli.py +532 -0
- flowyml/cli/ui.py +33 -0
- flowyml/core/__init__.py +68 -0
- flowyml/core/advanced_cache.py +274 -0
- flowyml/core/approval.py +64 -0
- flowyml/core/cache.py +203 -0
- flowyml/core/checkpoint.py +148 -0
- flowyml/core/conditional.py +373 -0
- flowyml/core/context.py +155 -0
- flowyml/core/error_handling.py +419 -0
- flowyml/core/executor.py +354 -0
- flowyml/core/graph.py +185 -0
- flowyml/core/parallel.py +452 -0
- flowyml/core/pipeline.py +764 -0
- flowyml/core/project.py +253 -0
- flowyml/core/resources.py +424 -0
- flowyml/core/scheduler.py +630 -0
- flowyml/core/scheduler_config.py +32 -0
- flowyml/core/step.py +201 -0
- flowyml/core/step_grouping.py +292 -0
- flowyml/core/templates.py +226 -0
- flowyml/core/versioning.py +217 -0
- flowyml/integrations/__init__.py +1 -0
- flowyml/integrations/keras.py +134 -0
- flowyml/monitoring/__init__.py +1 -0
- flowyml/monitoring/alerts.py +57 -0
- flowyml/monitoring/data.py +102 -0
- flowyml/monitoring/llm.py +160 -0
- flowyml/monitoring/monitor.py +57 -0
- flowyml/monitoring/notifications.py +246 -0
- flowyml/registry/__init__.py +5 -0
- flowyml/registry/model_registry.py +491 -0
- flowyml/registry/pipeline_registry.py +55 -0
- flowyml/stacks/__init__.py +27 -0
- flowyml/stacks/base.py +77 -0
- flowyml/stacks/bridge.py +288 -0
- flowyml/stacks/components.py +155 -0
- flowyml/stacks/gcp.py +499 -0
- flowyml/stacks/local.py +112 -0
- flowyml/stacks/migration.py +97 -0
- flowyml/stacks/plugin_config.py +78 -0
- flowyml/stacks/plugins.py +401 -0
- flowyml/stacks/registry.py +226 -0
- flowyml/storage/__init__.py +26 -0
- flowyml/storage/artifacts.py +246 -0
- flowyml/storage/materializers/__init__.py +20 -0
- flowyml/storage/materializers/base.py +133 -0
- flowyml/storage/materializers/keras.py +185 -0
- flowyml/storage/materializers/numpy.py +94 -0
- flowyml/storage/materializers/pandas.py +142 -0
- flowyml/storage/materializers/pytorch.py +135 -0
- flowyml/storage/materializers/sklearn.py +110 -0
- flowyml/storage/materializers/tensorflow.py +152 -0
- flowyml/storage/metadata.py +931 -0
- flowyml/tracking/__init__.py +1 -0
- flowyml/tracking/experiment.py +211 -0
- flowyml/tracking/leaderboard.py +191 -0
- flowyml/tracking/runs.py +145 -0
- flowyml/ui/__init__.py +15 -0
- flowyml/ui/backend/Dockerfile +31 -0
- flowyml/ui/backend/__init__.py +0 -0
- flowyml/ui/backend/auth.py +163 -0
- flowyml/ui/backend/main.py +187 -0
- flowyml/ui/backend/routers/__init__.py +0 -0
- flowyml/ui/backend/routers/assets.py +45 -0
- flowyml/ui/backend/routers/execution.py +179 -0
- flowyml/ui/backend/routers/experiments.py +49 -0
- flowyml/ui/backend/routers/leaderboard.py +118 -0
- flowyml/ui/backend/routers/notifications.py +72 -0
- flowyml/ui/backend/routers/pipelines.py +110 -0
- flowyml/ui/backend/routers/plugins.py +192 -0
- flowyml/ui/backend/routers/projects.py +85 -0
- flowyml/ui/backend/routers/runs.py +66 -0
- flowyml/ui/backend/routers/schedules.py +222 -0
- flowyml/ui/backend/routers/traces.py +84 -0
- flowyml/ui/frontend/Dockerfile +20 -0
- flowyml/ui/frontend/README.md +315 -0
- flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
- flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
- flowyml/ui/frontend/dist/index.html +16 -0
- flowyml/ui/frontend/index.html +15 -0
- flowyml/ui/frontend/nginx.conf +26 -0
- flowyml/ui/frontend/package-lock.json +3545 -0
- flowyml/ui/frontend/package.json +33 -0
- flowyml/ui/frontend/postcss.config.js +6 -0
- flowyml/ui/frontend/src/App.jsx +21 -0
- flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
- flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
- flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
- flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
- flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
- flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
- flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
- flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
- flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
- flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
- flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
- flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
- flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
- flowyml/ui/frontend/src/components/Layout.jsx +108 -0
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
- flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
- flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
- flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
- flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
- flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
- flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
- flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
- flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
- flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
- flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
- flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
- flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
- flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
- flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
- flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
- flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
- flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
- flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
- flowyml/ui/frontend/src/index.css +11 -0
- flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
- flowyml/ui/frontend/src/main.jsx +10 -0
- flowyml/ui/frontend/src/router/index.jsx +39 -0
- flowyml/ui/frontend/src/services/pluginService.js +90 -0
- flowyml/ui/frontend/src/utils/api.js +47 -0
- flowyml/ui/frontend/src/utils/cn.js +6 -0
- flowyml/ui/frontend/tailwind.config.js +31 -0
- flowyml/ui/frontend/vite.config.js +21 -0
- flowyml/ui/utils.py +77 -0
- flowyml/utils/__init__.py +67 -0
- flowyml/utils/config.py +308 -0
- flowyml/utils/debug.py +240 -0
- flowyml/utils/environment.py +346 -0
- flowyml/utils/git.py +319 -0
- flowyml/utils/logging.py +61 -0
- flowyml/utils/performance.py +314 -0
- flowyml/utils/stack_config.py +296 -0
- flowyml/utils/validation.py +270 -0
- flowyml-1.1.0.dist-info/METADATA +372 -0
- flowyml-1.1.0.dist-info/RECORD +159 -0
- flowyml-1.1.0.dist-info/WHEEL +4 -0
- flowyml-1.1.0.dist-info/entry_points.txt +3 -0
- flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
flowyml/stacks/bridge.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Generic Bridge for adapting external components to flowyml.
|
|
2
|
+
|
|
3
|
+
This module provides a universal bridge that can wrap components from other
|
|
4
|
+
frameworks (ZenML, Airflow, Prefect, etc.) using rule-based adaptation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from flowyml.stacks.components import (
|
|
13
|
+
StackComponent,
|
|
14
|
+
Orchestrator,
|
|
15
|
+
ArtifactStore,
|
|
16
|
+
ContainerRegistry,
|
|
17
|
+
ComponentType,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class AdaptationRule:
|
|
25
|
+
"""Rule for adapting an external component."""
|
|
26
|
+
|
|
27
|
+
# Matching criteria
|
|
28
|
+
source_type: Optional[str] = None # e.g., "zenml.orchestrators.base.BaseOrchestrator"
|
|
29
|
+
name_pattern: Optional[str] = None # e.g., ".*Orchestrator"
|
|
30
|
+
has_methods: list[str] = field(default_factory=list) # e.g., ["run", "prepare_pipeline"]
|
|
31
|
+
|
|
32
|
+
# Adaptation logic
|
|
33
|
+
target_type: ComponentType = ComponentType.ORCHESTRATOR
|
|
34
|
+
method_mapping: dict[str, str] = field(default_factory=dict) # flowyml_method -> external_method
|
|
35
|
+
attribute_mapping: dict[str, str] = field(default_factory=dict) # flowyml_attr -> external_attr
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GenericBridge:
|
|
39
|
+
"""Universal bridge for external components."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, rules: list[AdaptationRule] | None = None):
|
|
42
|
+
self.rules = rules or []
|
|
43
|
+
|
|
44
|
+
def is_available(self) -> bool:
|
|
45
|
+
"""Check if the bridge is available (always true for generic)."""
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
def wrap_component(
|
|
49
|
+
self,
|
|
50
|
+
external_class: Any,
|
|
51
|
+
name: str,
|
|
52
|
+
config: Optional[dict[str, Any]] = None,
|
|
53
|
+
) -> type[StackComponent]:
|
|
54
|
+
"""Dynamically create a wrapper class based on rules.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
external_class: The external component class to wrap
|
|
58
|
+
name: Name for the component
|
|
59
|
+
config: Optional configuration dictionary
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
A flowyml StackComponent class that wraps the external component
|
|
63
|
+
"""
|
|
64
|
+
# Determine component type and applicable rule
|
|
65
|
+
rule = self._find_matching_rule(external_class)
|
|
66
|
+
component_type = rule.target_type if rule else self._infer_component_type(external_class)
|
|
67
|
+
|
|
68
|
+
# Create the wrapper class dynamically
|
|
69
|
+
if component_type == ComponentType.ORCHESTRATOR:
|
|
70
|
+
return self._create_orchestrator_wrapper(external_class, name, rule)
|
|
71
|
+
elif component_type == ComponentType.ARTIFACT_STORE:
|
|
72
|
+
return self._create_artifact_store_wrapper(external_class, name, rule)
|
|
73
|
+
elif component_type == ComponentType.CONTAINER_REGISTRY:
|
|
74
|
+
return self._create_container_registry_wrapper(external_class, name, rule)
|
|
75
|
+
else:
|
|
76
|
+
return self._create_generic_wrapper(external_class, name, component_type, rule)
|
|
77
|
+
|
|
78
|
+
def _find_matching_rule(self, external_class: Any) -> Optional[AdaptationRule]:
|
|
79
|
+
"""Find the first rule that matches the external class."""
|
|
80
|
+
for rule in self.rules:
|
|
81
|
+
# Check source type
|
|
82
|
+
if rule.source_type:
|
|
83
|
+
# Check inheritance hierarchy names
|
|
84
|
+
mro_names = [c.__module__ + "." + c.__name__ for c in inspect.getmro(external_class)]
|
|
85
|
+
if rule.source_type not in mro_names:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
# Check name pattern
|
|
89
|
+
if rule.name_pattern:
|
|
90
|
+
import re
|
|
91
|
+
|
|
92
|
+
if not re.match(rule.name_pattern, external_class.__name__):
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# Check methods
|
|
96
|
+
if rule.has_methods:
|
|
97
|
+
has_all = True
|
|
98
|
+
for method in rule.has_methods:
|
|
99
|
+
if not hasattr(external_class, method):
|
|
100
|
+
has_all = False
|
|
101
|
+
break
|
|
102
|
+
if not has_all:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
return rule
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _infer_component_type(self, external_class: Any) -> ComponentType:
|
|
109
|
+
"""Infer component type if no rule matches."""
|
|
110
|
+
name = external_class.__name__.lower()
|
|
111
|
+
if "orchestrator" in name:
|
|
112
|
+
return ComponentType.ORCHESTRATOR
|
|
113
|
+
elif "artifact" in name and "store" in name:
|
|
114
|
+
return ComponentType.ARTIFACT_STORE
|
|
115
|
+
elif "container" in name and "registry" in name:
|
|
116
|
+
return ComponentType.CONTAINER_REGISTRY
|
|
117
|
+
return ComponentType.ORCHESTRATOR # Default
|
|
118
|
+
|
|
119
|
+
def _create_orchestrator_wrapper(
|
|
120
|
+
self,
|
|
121
|
+
external_class: Any,
|
|
122
|
+
name: str,
|
|
123
|
+
rule: Optional[AdaptationRule],
|
|
124
|
+
) -> type[Orchestrator]:
|
|
125
|
+
"""Create a wrapper for an Orchestrator."""
|
|
126
|
+
|
|
127
|
+
class GenericOrchestratorWrapper(Orchestrator):
|
|
128
|
+
def __init__(self, **kwargs):
|
|
129
|
+
super().__init__(name)
|
|
130
|
+
self._external_component = external_class(**kwargs)
|
|
131
|
+
self._rule = rule
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def component_type(self) -> ComponentType:
|
|
135
|
+
return ComponentType.ORCHESTRATOR
|
|
136
|
+
|
|
137
|
+
def validate(self) -> bool:
|
|
138
|
+
# Try to find a validate method
|
|
139
|
+
if hasattr(self._external_component, "validate"):
|
|
140
|
+
return self._external_component.validate()
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
def to_dict(self) -> dict[str, Any]:
|
|
144
|
+
if hasattr(self._external_component, "dict"):
|
|
145
|
+
return self._external_component.dict()
|
|
146
|
+
if hasattr(self._external_component, "to_dict"):
|
|
147
|
+
return self._external_component.to_dict()
|
|
148
|
+
return {"name": self.name, "type": "generic_wrapper"}
|
|
149
|
+
|
|
150
|
+
def run_pipeline(self, pipeline: Any, **kwargs) -> Any:
|
|
151
|
+
"""Delegate pipeline execution."""
|
|
152
|
+
method_name = "run"
|
|
153
|
+
if self._rule and "run_pipeline" in self._rule.method_mapping:
|
|
154
|
+
method_name = self._rule.method_mapping["run_pipeline"]
|
|
155
|
+
|
|
156
|
+
if hasattr(self._external_component, method_name):
|
|
157
|
+
method = getattr(self._external_component, method_name)
|
|
158
|
+
return method(pipeline, **kwargs)
|
|
159
|
+
|
|
160
|
+
logger.warning(f"Orchestrator {self.name} does not have method '{method_name}'")
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def get_run_status(self, run_id: str) -> str:
|
|
164
|
+
return "unknown"
|
|
165
|
+
|
|
166
|
+
def __getattr__(self, name: str) -> Any:
|
|
167
|
+
"""Delegate unknown attributes to external component."""
|
|
168
|
+
return getattr(self._external_component, name)
|
|
169
|
+
|
|
170
|
+
GenericOrchestratorWrapper.__name__ = f"Wrapped{external_class.__name__}"
|
|
171
|
+
return GenericOrchestratorWrapper
|
|
172
|
+
|
|
173
|
+
def _create_artifact_store_wrapper(
|
|
174
|
+
self,
|
|
175
|
+
external_class: Any,
|
|
176
|
+
name: str,
|
|
177
|
+
rule: Optional[AdaptationRule],
|
|
178
|
+
) -> type[ArtifactStore]:
|
|
179
|
+
"""Create a wrapper for an Artifact Store."""
|
|
180
|
+
|
|
181
|
+
class GenericArtifactStoreWrapper(ArtifactStore):
|
|
182
|
+
def __init__(self, **kwargs):
|
|
183
|
+
super().__init__(name)
|
|
184
|
+
self._external_component = external_class(**kwargs)
|
|
185
|
+
self._rule = rule
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def component_type(self) -> ComponentType:
|
|
189
|
+
return ComponentType.ARTIFACT_STORE
|
|
190
|
+
|
|
191
|
+
def validate(self) -> bool:
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
def to_dict(self) -> dict[str, Any]:
|
|
195
|
+
if hasattr(self._external_component, "dict"):
|
|
196
|
+
return self._external_component.dict()
|
|
197
|
+
return {"name": self.name}
|
|
198
|
+
|
|
199
|
+
# Explicitly implement abstract methods to satisfy ABC
|
|
200
|
+
def save(self, artifact: Any, path: str) -> str:
|
|
201
|
+
return self._external_component.save(artifact, path)
|
|
202
|
+
|
|
203
|
+
def load(self, path: str) -> Any:
|
|
204
|
+
return self._external_component.load(path)
|
|
205
|
+
|
|
206
|
+
def exists(self, path: str) -> bool:
|
|
207
|
+
return self._external_component.exists(path)
|
|
208
|
+
|
|
209
|
+
def __getattr__(self, name: str) -> Any:
|
|
210
|
+
return getattr(self._external_component, name)
|
|
211
|
+
|
|
212
|
+
GenericArtifactStoreWrapper.__name__ = f"Wrapped{external_class.__name__}"
|
|
213
|
+
return GenericArtifactStoreWrapper
|
|
214
|
+
|
|
215
|
+
def _create_container_registry_wrapper(
|
|
216
|
+
self,
|
|
217
|
+
external_class: Any,
|
|
218
|
+
name: str,
|
|
219
|
+
rule: Optional[AdaptationRule],
|
|
220
|
+
) -> type[ContainerRegistry]:
|
|
221
|
+
"""Create a wrapper for a Container Registry."""
|
|
222
|
+
|
|
223
|
+
class GenericContainerRegistryWrapper(ContainerRegistry):
|
|
224
|
+
def __init__(self, **kwargs):
|
|
225
|
+
super().__init__(name)
|
|
226
|
+
self._external_component = external_class(**kwargs)
|
|
227
|
+
self._rule = rule
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def component_type(self) -> ComponentType:
|
|
231
|
+
return ComponentType.CONTAINER_REGISTRY
|
|
232
|
+
|
|
233
|
+
def validate(self) -> bool:
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
def to_dict(self) -> dict[str, Any]:
|
|
237
|
+
if hasattr(self._external_component, "dict"):
|
|
238
|
+
return self._external_component.dict()
|
|
239
|
+
return {"name": self.name}
|
|
240
|
+
|
|
241
|
+
# Explicitly implement abstract methods to satisfy ABC
|
|
242
|
+
def push_image(self, image_name: str, tag: str = "latest") -> str:
|
|
243
|
+
return self._external_component.push_image(image_name, tag)
|
|
244
|
+
|
|
245
|
+
def pull_image(self, image_name: str, tag: str = "latest") -> None:
|
|
246
|
+
return self._external_component.pull_image(image_name, tag)
|
|
247
|
+
|
|
248
|
+
def get_image_uri(self, image_name: str, tag: str = "latest") -> str:
|
|
249
|
+
return self._external_component.get_image_uri(image_name, tag)
|
|
250
|
+
|
|
251
|
+
def __getattr__(self, name: str) -> Any:
|
|
252
|
+
return getattr(self._external_component, name)
|
|
253
|
+
|
|
254
|
+
GenericContainerRegistryWrapper.__name__ = f"Wrapped{external_class.__name__}"
|
|
255
|
+
return GenericContainerRegistryWrapper
|
|
256
|
+
|
|
257
|
+
def _create_generic_wrapper(
|
|
258
|
+
self,
|
|
259
|
+
external_class: Any,
|
|
260
|
+
name: str,
|
|
261
|
+
comp_type: ComponentType,
|
|
262
|
+
rule: Optional[AdaptationRule],
|
|
263
|
+
) -> type[StackComponent]:
|
|
264
|
+
"""Create a generic wrapper."""
|
|
265
|
+
|
|
266
|
+
class GenericWrapper(StackComponent):
|
|
267
|
+
def __init__(self, **kwargs):
|
|
268
|
+
super().__init__(name)
|
|
269
|
+
self._external_component = external_class(**kwargs)
|
|
270
|
+
self._rule = rule
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def component_type(self) -> ComponentType:
|
|
274
|
+
return comp_type
|
|
275
|
+
|
|
276
|
+
def validate(self) -> bool:
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
def to_dict(self) -> dict[str, Any]:
|
|
280
|
+
if hasattr(self._external_component, "dict"):
|
|
281
|
+
return self._external_component.dict()
|
|
282
|
+
return {"name": self.name}
|
|
283
|
+
|
|
284
|
+
def __getattr__(self, name: str) -> Any:
|
|
285
|
+
return getattr(self._external_component, name)
|
|
286
|
+
|
|
287
|
+
GenericWrapper.__name__ = f"Wrapped{external_class.__name__}"
|
|
288
|
+
return GenericWrapper
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Stack Components - Reusable building blocks for stacks.
|
|
2
|
+
|
|
3
|
+
This module provides base classes for orchestrators, artifact stores,
|
|
4
|
+
container registries, and other stack components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComponentType(Enum):
|
|
14
|
+
"""Types of stack components."""
|
|
15
|
+
|
|
16
|
+
ORCHESTRATOR = "orchestrator"
|
|
17
|
+
ARTIFACT_STORE = "artifact_store"
|
|
18
|
+
CONTAINER_REGISTRY = "container_registry"
|
|
19
|
+
METADATA_STORE = "metadata_store"
|
|
20
|
+
EXECUTOR = "executor"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ResourceConfig:
|
|
25
|
+
"""Configuration for compute resources."""
|
|
26
|
+
|
|
27
|
+
cpu: str = "1" # e.g., "2", "4", "8"
|
|
28
|
+
memory: str = "2Gi" # e.g., "4Gi", "8Gi", "16Gi"
|
|
29
|
+
gpu: str | None = None # e.g., "nvidia-tesla-t4", "nvidia-tesla-v100"
|
|
30
|
+
gpu_count: int = 0
|
|
31
|
+
disk_size: str = "10Gi"
|
|
32
|
+
machine_type: str | None = None # Cloud-specific machine type
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> dict[str, Any]:
|
|
35
|
+
"""Convert to dictionary."""
|
|
36
|
+
return {
|
|
37
|
+
"cpu": self.cpu,
|
|
38
|
+
"memory": self.memory,
|
|
39
|
+
"gpu": self.gpu,
|
|
40
|
+
"gpu_count": self.gpu_count,
|
|
41
|
+
"disk_size": self.disk_size,
|
|
42
|
+
"machine_type": self.machine_type,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class DockerConfig:
|
|
48
|
+
"""Configuration for Docker containerization."""
|
|
49
|
+
|
|
50
|
+
image: str | None = None # e.g., "gcr.io/myproject/flowyml:latest"
|
|
51
|
+
dockerfile: str | None = None # Path to Dockerfile
|
|
52
|
+
build_context: str = "."
|
|
53
|
+
requirements: list[str] | None = None # Python requirements
|
|
54
|
+
base_image: str = "python:3.11-slim"
|
|
55
|
+
env_vars: dict[str, str] = field(default_factory=dict)
|
|
56
|
+
build_args: dict[str, str] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict[str, Any]:
|
|
59
|
+
"""Convert to dictionary."""
|
|
60
|
+
return {
|
|
61
|
+
"image": self.image,
|
|
62
|
+
"dockerfile": self.dockerfile,
|
|
63
|
+
"build_context": self.build_context,
|
|
64
|
+
"requirements": self.requirements or [],
|
|
65
|
+
"base_image": self.base_image,
|
|
66
|
+
"env_vars": self.env_vars,
|
|
67
|
+
"build_args": self.build_args,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class StackComponent(ABC):
|
|
72
|
+
"""Base class for all stack components."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, name: str):
|
|
75
|
+
self.name = name
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def component_type(self) -> ComponentType:
|
|
80
|
+
"""Return the type of this component."""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def validate(self) -> bool:
|
|
85
|
+
"""Validate component configuration."""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def to_dict(self) -> dict[str, Any]:
|
|
90
|
+
"""Convert component to dictionary."""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Orchestrator(StackComponent):
|
|
95
|
+
"""Base class for orchestrators."""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def component_type(self) -> ComponentType:
|
|
99
|
+
return ComponentType.ORCHESTRATOR
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def run_pipeline(self, pipeline: Any, **kwargs) -> Any:
|
|
103
|
+
"""Run a pipeline on this orchestrator."""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def get_run_status(self, run_id: str) -> str:
|
|
108
|
+
"""Get status of a pipeline run."""
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ArtifactStore(StackComponent):
|
|
113
|
+
"""Base class for artifact stores."""
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def component_type(self) -> ComponentType:
|
|
117
|
+
return ComponentType.ARTIFACT_STORE
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def save(self, artifact: Any, path: str) -> str:
|
|
121
|
+
"""Save artifact to store."""
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def load(self, path: str) -> Any:
|
|
126
|
+
"""Load artifact from store."""
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def exists(self, path: str) -> bool:
|
|
131
|
+
"""Check if artifact exists."""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ContainerRegistry(StackComponent):
|
|
136
|
+
"""Base class for container registries."""
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def component_type(self) -> ComponentType:
|
|
140
|
+
return ComponentType.CONTAINER_REGISTRY
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def push_image(self, image_name: str, tag: str = "latest") -> str:
|
|
144
|
+
"""Push Docker image to registry."""
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
@abstractmethod
|
|
148
|
+
def pull_image(self, image_name: str, tag: str = "latest") -> None:
|
|
149
|
+
"""Pull Docker image from registry."""
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def get_image_uri(self, image_name: str, tag: str = "latest") -> str:
|
|
154
|
+
"""Get full URI for an image."""
|
|
155
|
+
pass
|