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.
Files changed (159) hide show
  1. flowyml/__init__.py +207 -0
  2. flowyml/assets/__init__.py +22 -0
  3. flowyml/assets/artifact.py +40 -0
  4. flowyml/assets/base.py +209 -0
  5. flowyml/assets/dataset.py +100 -0
  6. flowyml/assets/featureset.py +301 -0
  7. flowyml/assets/metrics.py +104 -0
  8. flowyml/assets/model.py +82 -0
  9. flowyml/assets/registry.py +157 -0
  10. flowyml/assets/report.py +315 -0
  11. flowyml/cli/__init__.py +5 -0
  12. flowyml/cli/experiment.py +232 -0
  13. flowyml/cli/init.py +256 -0
  14. flowyml/cli/main.py +327 -0
  15. flowyml/cli/run.py +75 -0
  16. flowyml/cli/stack_cli.py +532 -0
  17. flowyml/cli/ui.py +33 -0
  18. flowyml/core/__init__.py +68 -0
  19. flowyml/core/advanced_cache.py +274 -0
  20. flowyml/core/approval.py +64 -0
  21. flowyml/core/cache.py +203 -0
  22. flowyml/core/checkpoint.py +148 -0
  23. flowyml/core/conditional.py +373 -0
  24. flowyml/core/context.py +155 -0
  25. flowyml/core/error_handling.py +419 -0
  26. flowyml/core/executor.py +354 -0
  27. flowyml/core/graph.py +185 -0
  28. flowyml/core/parallel.py +452 -0
  29. flowyml/core/pipeline.py +764 -0
  30. flowyml/core/project.py +253 -0
  31. flowyml/core/resources.py +424 -0
  32. flowyml/core/scheduler.py +630 -0
  33. flowyml/core/scheduler_config.py +32 -0
  34. flowyml/core/step.py +201 -0
  35. flowyml/core/step_grouping.py +292 -0
  36. flowyml/core/templates.py +226 -0
  37. flowyml/core/versioning.py +217 -0
  38. flowyml/integrations/__init__.py +1 -0
  39. flowyml/integrations/keras.py +134 -0
  40. flowyml/monitoring/__init__.py +1 -0
  41. flowyml/monitoring/alerts.py +57 -0
  42. flowyml/monitoring/data.py +102 -0
  43. flowyml/monitoring/llm.py +160 -0
  44. flowyml/monitoring/monitor.py +57 -0
  45. flowyml/monitoring/notifications.py +246 -0
  46. flowyml/registry/__init__.py +5 -0
  47. flowyml/registry/model_registry.py +491 -0
  48. flowyml/registry/pipeline_registry.py +55 -0
  49. flowyml/stacks/__init__.py +27 -0
  50. flowyml/stacks/base.py +77 -0
  51. flowyml/stacks/bridge.py +288 -0
  52. flowyml/stacks/components.py +155 -0
  53. flowyml/stacks/gcp.py +499 -0
  54. flowyml/stacks/local.py +112 -0
  55. flowyml/stacks/migration.py +97 -0
  56. flowyml/stacks/plugin_config.py +78 -0
  57. flowyml/stacks/plugins.py +401 -0
  58. flowyml/stacks/registry.py +226 -0
  59. flowyml/storage/__init__.py +26 -0
  60. flowyml/storage/artifacts.py +246 -0
  61. flowyml/storage/materializers/__init__.py +20 -0
  62. flowyml/storage/materializers/base.py +133 -0
  63. flowyml/storage/materializers/keras.py +185 -0
  64. flowyml/storage/materializers/numpy.py +94 -0
  65. flowyml/storage/materializers/pandas.py +142 -0
  66. flowyml/storage/materializers/pytorch.py +135 -0
  67. flowyml/storage/materializers/sklearn.py +110 -0
  68. flowyml/storage/materializers/tensorflow.py +152 -0
  69. flowyml/storage/metadata.py +931 -0
  70. flowyml/tracking/__init__.py +1 -0
  71. flowyml/tracking/experiment.py +211 -0
  72. flowyml/tracking/leaderboard.py +191 -0
  73. flowyml/tracking/runs.py +145 -0
  74. flowyml/ui/__init__.py +15 -0
  75. flowyml/ui/backend/Dockerfile +31 -0
  76. flowyml/ui/backend/__init__.py +0 -0
  77. flowyml/ui/backend/auth.py +163 -0
  78. flowyml/ui/backend/main.py +187 -0
  79. flowyml/ui/backend/routers/__init__.py +0 -0
  80. flowyml/ui/backend/routers/assets.py +45 -0
  81. flowyml/ui/backend/routers/execution.py +179 -0
  82. flowyml/ui/backend/routers/experiments.py +49 -0
  83. flowyml/ui/backend/routers/leaderboard.py +118 -0
  84. flowyml/ui/backend/routers/notifications.py +72 -0
  85. flowyml/ui/backend/routers/pipelines.py +110 -0
  86. flowyml/ui/backend/routers/plugins.py +192 -0
  87. flowyml/ui/backend/routers/projects.py +85 -0
  88. flowyml/ui/backend/routers/runs.py +66 -0
  89. flowyml/ui/backend/routers/schedules.py +222 -0
  90. flowyml/ui/backend/routers/traces.py +84 -0
  91. flowyml/ui/frontend/Dockerfile +20 -0
  92. flowyml/ui/frontend/README.md +315 -0
  93. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +448 -0
  94. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +1 -0
  95. flowyml/ui/frontend/dist/index.html +16 -0
  96. flowyml/ui/frontend/index.html +15 -0
  97. flowyml/ui/frontend/nginx.conf +26 -0
  98. flowyml/ui/frontend/package-lock.json +3545 -0
  99. flowyml/ui/frontend/package.json +33 -0
  100. flowyml/ui/frontend/postcss.config.js +6 -0
  101. flowyml/ui/frontend/src/App.jsx +21 -0
  102. flowyml/ui/frontend/src/app/assets/page.jsx +397 -0
  103. flowyml/ui/frontend/src/app/dashboard/page.jsx +295 -0
  104. flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +255 -0
  105. flowyml/ui/frontend/src/app/experiments/page.jsx +360 -0
  106. flowyml/ui/frontend/src/app/leaderboard/page.jsx +133 -0
  107. flowyml/ui/frontend/src/app/pipelines/page.jsx +454 -0
  108. flowyml/ui/frontend/src/app/plugins/page.jsx +48 -0
  109. flowyml/ui/frontend/src/app/projects/page.jsx +292 -0
  110. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +682 -0
  111. flowyml/ui/frontend/src/app/runs/page.jsx +470 -0
  112. flowyml/ui/frontend/src/app/schedules/page.jsx +585 -0
  113. flowyml/ui/frontend/src/app/settings/page.jsx +314 -0
  114. flowyml/ui/frontend/src/app/tokens/page.jsx +456 -0
  115. flowyml/ui/frontend/src/app/traces/page.jsx +246 -0
  116. flowyml/ui/frontend/src/components/Layout.jsx +108 -0
  117. flowyml/ui/frontend/src/components/PipelineGraph.jsx +295 -0
  118. flowyml/ui/frontend/src/components/header/Header.jsx +72 -0
  119. flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +121 -0
  120. flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +124 -0
  121. flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +167 -0
  122. flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +60 -0
  123. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +145 -0
  124. flowyml/ui/frontend/src/components/ui/Badge.jsx +26 -0
  125. flowyml/ui/frontend/src/components/ui/Button.jsx +34 -0
  126. flowyml/ui/frontend/src/components/ui/Card.jsx +44 -0
  127. flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +38 -0
  128. flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +53 -0
  129. flowyml/ui/frontend/src/components/ui/DataView.jsx +175 -0
  130. flowyml/ui/frontend/src/components/ui/EmptyState.jsx +49 -0
  131. flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +122 -0
  132. flowyml/ui/frontend/src/components/ui/KeyValue.jsx +25 -0
  133. flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +134 -0
  134. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +79 -0
  135. flowyml/ui/frontend/src/contexts/ThemeContext.jsx +54 -0
  136. flowyml/ui/frontend/src/index.css +11 -0
  137. flowyml/ui/frontend/src/layouts/MainLayout.jsx +23 -0
  138. flowyml/ui/frontend/src/main.jsx +10 -0
  139. flowyml/ui/frontend/src/router/index.jsx +39 -0
  140. flowyml/ui/frontend/src/services/pluginService.js +90 -0
  141. flowyml/ui/frontend/src/utils/api.js +47 -0
  142. flowyml/ui/frontend/src/utils/cn.js +6 -0
  143. flowyml/ui/frontend/tailwind.config.js +31 -0
  144. flowyml/ui/frontend/vite.config.js +21 -0
  145. flowyml/ui/utils.py +77 -0
  146. flowyml/utils/__init__.py +67 -0
  147. flowyml/utils/config.py +308 -0
  148. flowyml/utils/debug.py +240 -0
  149. flowyml/utils/environment.py +346 -0
  150. flowyml/utils/git.py +319 -0
  151. flowyml/utils/logging.py +61 -0
  152. flowyml/utils/performance.py +314 -0
  153. flowyml/utils/stack_config.py +296 -0
  154. flowyml/utils/validation.py +270 -0
  155. flowyml-1.1.0.dist-info/METADATA +372 -0
  156. flowyml-1.1.0.dist-info/RECORD +159 -0
  157. flowyml-1.1.0.dist-info/WHEEL +4 -0
  158. flowyml-1.1.0.dist-info/entry_points.txt +3 -0
  159. flowyml-1.1.0.dist-info/licenses/LICENSE +17 -0
@@ -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