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
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Configuration loader for flowyml stacks.
|
|
2
|
+
|
|
3
|
+
Loads stack configurations from YAML files, environment variables,
|
|
4
|
+
and provides defaults.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import yaml
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class StackConfigFile:
|
|
16
|
+
"""Stack configuration loaded from flowyml.yaml.
|
|
17
|
+
|
|
18
|
+
Example flowyml.yaml:
|
|
19
|
+
```yaml
|
|
20
|
+
stacks:
|
|
21
|
+
local:
|
|
22
|
+
type: local
|
|
23
|
+
artifact_store:
|
|
24
|
+
path: .flowyml/artifacts
|
|
25
|
+
metadata_store:
|
|
26
|
+
path: .flowyml/metadata.db
|
|
27
|
+
|
|
28
|
+
production:
|
|
29
|
+
type: gcp
|
|
30
|
+
project_id: ${GCP_PROJECT_ID}
|
|
31
|
+
region: us-central1
|
|
32
|
+
artifact_store:
|
|
33
|
+
type: gcs
|
|
34
|
+
bucket: ${GCP_BUCKET}
|
|
35
|
+
container_registry:
|
|
36
|
+
type: gcr
|
|
37
|
+
uri: gcr.io/${GCP_PROJECT_ID}
|
|
38
|
+
orchestrator:
|
|
39
|
+
type: vertex_ai
|
|
40
|
+
|
|
41
|
+
resources:
|
|
42
|
+
default:
|
|
43
|
+
cpu: "2"
|
|
44
|
+
memory: "8Gi"
|
|
45
|
+
|
|
46
|
+
gpu_training:
|
|
47
|
+
cpu: "8"
|
|
48
|
+
memory: "32Gi"
|
|
49
|
+
gpu: nvidia-tesla-v100
|
|
50
|
+
gpu_count: 2
|
|
51
|
+
|
|
52
|
+
docker:
|
|
53
|
+
dockerfile: ./Dockerfile
|
|
54
|
+
build_context: .
|
|
55
|
+
use_poetry: true
|
|
56
|
+
requirements_file: requirements.txt
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
stacks: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
61
|
+
resources: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
62
|
+
docker: dict[str, Any] = field(default_factory=dict)
|
|
63
|
+
default_stack: str | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ConfigLoader:
|
|
67
|
+
"""Load and manage flowyml configurations."""
|
|
68
|
+
|
|
69
|
+
DEFAULT_CONFIG_FILES = [
|
|
70
|
+
"flowyml.yaml",
|
|
71
|
+
"flowyml.yml",
|
|
72
|
+
".flowyml/config.yaml",
|
|
73
|
+
".flowyml/config.yml",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def __init__(self, config_path: str | None = None):
|
|
77
|
+
"""Initialize configuration loader.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
config_path: Path to configuration file. If None, searches default locations.
|
|
81
|
+
"""
|
|
82
|
+
self.config_path = self._find_config_file(config_path)
|
|
83
|
+
self.config: StackConfigFile | None = None
|
|
84
|
+
|
|
85
|
+
if self.config_path:
|
|
86
|
+
self.load()
|
|
87
|
+
|
|
88
|
+
def _find_config_file(self, config_path: str | None = None) -> Path | None:
|
|
89
|
+
"""Find configuration file."""
|
|
90
|
+
if config_path:
|
|
91
|
+
path = Path(config_path)
|
|
92
|
+
if path.exists():
|
|
93
|
+
return path
|
|
94
|
+
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
95
|
+
|
|
96
|
+
# Search default locations
|
|
97
|
+
for default_path in self.DEFAULT_CONFIG_FILES:
|
|
98
|
+
path = Path(default_path)
|
|
99
|
+
if path.exists():
|
|
100
|
+
return path
|
|
101
|
+
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def load(self) -> StackConfigFile:
|
|
105
|
+
"""Load configuration from file."""
|
|
106
|
+
if not self.config_path:
|
|
107
|
+
# Return empty config
|
|
108
|
+
self.config = StackConfigFile()
|
|
109
|
+
return self.config
|
|
110
|
+
|
|
111
|
+
with open(self.config_path) as f:
|
|
112
|
+
data = yaml.safe_load(f) or {}
|
|
113
|
+
|
|
114
|
+
# Expand environment variables
|
|
115
|
+
data = self._expand_env_vars(data)
|
|
116
|
+
|
|
117
|
+
self.config = StackConfigFile(
|
|
118
|
+
stacks=data.get("stacks", {}),
|
|
119
|
+
resources=data.get("resources", {}),
|
|
120
|
+
docker=data.get("docker", {}),
|
|
121
|
+
default_stack=data.get("default_stack"),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return self.config
|
|
125
|
+
|
|
126
|
+
def _expand_env_vars(self, data: Any) -> Any:
|
|
127
|
+
"""Recursively expand environment variables in config."""
|
|
128
|
+
if isinstance(data, dict):
|
|
129
|
+
return {k: self._expand_env_vars(v) for k, v in data.items()}
|
|
130
|
+
elif isinstance(data, list):
|
|
131
|
+
return [self._expand_env_vars(item) for item in data]
|
|
132
|
+
elif isinstance(data, str):
|
|
133
|
+
# Expand ${VAR} or $VAR
|
|
134
|
+
import re
|
|
135
|
+
|
|
136
|
+
def replace_var(match):
|
|
137
|
+
var_name = match.group(1) or match.group(2)
|
|
138
|
+
return os.getenv(var_name, match.group(0))
|
|
139
|
+
|
|
140
|
+
pattern = r"\$\{([^}]+)\}|\$([A-Z_][A-Z0-9_]*)"
|
|
141
|
+
return re.sub(pattern, replace_var, data)
|
|
142
|
+
else:
|
|
143
|
+
return data
|
|
144
|
+
|
|
145
|
+
def get_stack_config(self, stack_name: str) -> dict[str, Any] | None:
|
|
146
|
+
"""Get configuration for a specific stack."""
|
|
147
|
+
if not self.config:
|
|
148
|
+
return None
|
|
149
|
+
return self.config.stacks.get(stack_name)
|
|
150
|
+
|
|
151
|
+
def get_resource_config(self, resource_name: str = "default") -> dict[str, Any] | None:
|
|
152
|
+
"""Get resource configuration."""
|
|
153
|
+
if not self.config:
|
|
154
|
+
return None
|
|
155
|
+
return self.config.resources.get(resource_name)
|
|
156
|
+
|
|
157
|
+
def get_docker_config(self) -> dict[str, Any]:
|
|
158
|
+
"""Get Docker configuration."""
|
|
159
|
+
if not self.config:
|
|
160
|
+
return self._get_default_docker_config()
|
|
161
|
+
return self.config.docker or self._get_default_docker_config()
|
|
162
|
+
|
|
163
|
+
def _get_default_docker_config(self) -> dict[str, Any]:
|
|
164
|
+
"""Get default Docker configuration."""
|
|
165
|
+
# Auto-detect Dockerfile
|
|
166
|
+
dockerfile = None
|
|
167
|
+
for possible_dockerfile in ["Dockerfile", "docker/Dockerfile", ".docker/Dockerfile"]:
|
|
168
|
+
if Path(possible_dockerfile).exists():
|
|
169
|
+
dockerfile = possible_dockerfile
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
# Check for poetry
|
|
173
|
+
use_poetry = Path("pyproject.toml").exists()
|
|
174
|
+
|
|
175
|
+
# Check for requirements.txt
|
|
176
|
+
requirements_file = "requirements.txt" if Path("requirements.txt").exists() else None
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"dockerfile": dockerfile,
|
|
180
|
+
"build_context": ".",
|
|
181
|
+
"use_poetry": use_poetry,
|
|
182
|
+
"requirements_file": requirements_file,
|
|
183
|
+
"base_image": "python:3.11-slim",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
def list_stacks(self) -> list[str]:
|
|
187
|
+
"""List all configured stacks."""
|
|
188
|
+
if not self.config:
|
|
189
|
+
return []
|
|
190
|
+
return list(self.config.stacks.keys())
|
|
191
|
+
|
|
192
|
+
def get_default_stack(self) -> str | None:
|
|
193
|
+
"""Get the default stack name."""
|
|
194
|
+
if not self.config:
|
|
195
|
+
return None
|
|
196
|
+
return self.config.default_stack or (list(self.config.stacks.keys())[0] if self.config.stacks else None)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def load_config(config_path: str | None = None) -> ConfigLoader:
|
|
200
|
+
"""Load flowyml configuration.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
config_path: Path to config file. If None, searches default locations.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
ConfigLoader instance
|
|
207
|
+
"""
|
|
208
|
+
return ConfigLoader(config_path)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def create_stack_from_config(config: dict[str, Any], name: str):
|
|
212
|
+
"""Create a stack instance from configuration dictionary.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
config: Stack configuration dictionary
|
|
216
|
+
name: Stack name
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Stack instance
|
|
220
|
+
"""
|
|
221
|
+
stack_type = config.get("type", "local")
|
|
222
|
+
|
|
223
|
+
if stack_type == "local":
|
|
224
|
+
from flowyml.stacks.local import LocalStack
|
|
225
|
+
|
|
226
|
+
return LocalStack(
|
|
227
|
+
name=name,
|
|
228
|
+
artifact_path=config.get("artifact_store", {}).get("path", ".flowyml/artifacts"),
|
|
229
|
+
metadata_path=config.get("metadata_store", {}).get("path", ".flowyml/metadata.db"),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
elif stack_type == "gcp":
|
|
233
|
+
from flowyml.stacks.gcp import GCPStack
|
|
234
|
+
|
|
235
|
+
return GCPStack(
|
|
236
|
+
name=name,
|
|
237
|
+
project_id=config.get("project_id"),
|
|
238
|
+
region=config.get("region", "us-central1"),
|
|
239
|
+
bucket_name=config.get("artifact_store", {}).get("bucket"),
|
|
240
|
+
registry_uri=config.get("container_registry", {}).get("uri"),
|
|
241
|
+
service_account=config.get("orchestrator", {}).get("service_account"),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
raise ValueError(f"Unknown stack type: {stack_type}")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def create_resource_config_from_dict(config: dict[str, Any]):
|
|
249
|
+
"""Create ResourceConfig from dictionary."""
|
|
250
|
+
from flowyml.stacks.components import ResourceConfig
|
|
251
|
+
|
|
252
|
+
return ResourceConfig(**config)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def create_docker_config_from_dict(config: dict[str, Any]):
|
|
256
|
+
"""Create DockerConfig from dictionary."""
|
|
257
|
+
from flowyml.stacks.components import DockerConfig
|
|
258
|
+
|
|
259
|
+
# Handle poetry configuration
|
|
260
|
+
if config.get("use_poetry"):
|
|
261
|
+
# Read dependencies from pyproject.toml
|
|
262
|
+
import toml
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
with open("pyproject.toml") as f:
|
|
266
|
+
pyproject = toml.load(f)
|
|
267
|
+
|
|
268
|
+
# Extract dependencies
|
|
269
|
+
deps = pyproject.get("tool", {}).get("poetry", {}).get("dependencies", {})
|
|
270
|
+
requirements = [
|
|
271
|
+
f"{name}>={version.replace('^', '')}"
|
|
272
|
+
for name, version in deps.items()
|
|
273
|
+
if name != "python" and isinstance(version, str)
|
|
274
|
+
]
|
|
275
|
+
config["requirements"] = requirements
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
# Handle requirements file
|
|
280
|
+
elif config.get("requirements_file"):
|
|
281
|
+
try:
|
|
282
|
+
with open(config["requirements_file"]) as f:
|
|
283
|
+
requirements = [line.strip() for line in f if line.strip() and not line.startswith("#")]
|
|
284
|
+
config["requirements"] = requirements
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
return DockerConfig(
|
|
289
|
+
image=config.get("image"),
|
|
290
|
+
dockerfile=config.get("dockerfile"),
|
|
291
|
+
build_context=config.get("build_context", "."),
|
|
292
|
+
requirements=config.get("requirements"),
|
|
293
|
+
base_image=config.get("base_image", "python:3.11-slim"),
|
|
294
|
+
env_vars=config.get("env_vars", {}),
|
|
295
|
+
build_args=config.get("build_args", {}),
|
|
296
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Pydantic schemas and validation utilities for flowyml."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CacheStrategy(str, Enum):
|
|
9
|
+
"""Cache strategy options."""
|
|
10
|
+
|
|
11
|
+
CODE_HASH = "code_hash"
|
|
12
|
+
INPUT_HASH = "input_hash"
|
|
13
|
+
SEMANTIC = "semantic"
|
|
14
|
+
CUSTOM = "custom"
|
|
15
|
+
NONE = "none"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ResourceRequirements(BaseModel):
|
|
19
|
+
"""Resource requirements for step execution."""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(extra="forbid")
|
|
22
|
+
|
|
23
|
+
cpus: int | None = Field(None, ge=1, description="Number of CPUs required")
|
|
24
|
+
memory: str | None = Field(None, description="Memory required (e.g., '4GB', '512MB')")
|
|
25
|
+
gpus: int | None = Field(None, ge=0, description="Number of GPUs required")
|
|
26
|
+
disk: str | None = Field(None, description="Disk space required")
|
|
27
|
+
|
|
28
|
+
@field_validator("memory", "disk")
|
|
29
|
+
@classmethod
|
|
30
|
+
def validate_size_format(cls, v: str | None) -> str | None:
|
|
31
|
+
"""Validate memory/disk size format."""
|
|
32
|
+
if v is None:
|
|
33
|
+
return v
|
|
34
|
+
|
|
35
|
+
valid_units = ["B", "KB", "MB", "GB", "TB"]
|
|
36
|
+
v_upper = v.upper()
|
|
37
|
+
|
|
38
|
+
for unit in valid_units:
|
|
39
|
+
if v_upper.endswith(unit):
|
|
40
|
+
try:
|
|
41
|
+
size_val = v_upper[: -len(unit)]
|
|
42
|
+
float(size_val)
|
|
43
|
+
return v
|
|
44
|
+
except ValueError:
|
|
45
|
+
raise ValueError(f"Invalid size format: {v}. Expected format: <number><unit> (e.g., '4GB')")
|
|
46
|
+
|
|
47
|
+
raise ValueError(f"Invalid size unit. Must be one of: {', '.join(valid_units)}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RetryConfig(BaseModel):
|
|
51
|
+
"""Retry configuration for step execution."""
|
|
52
|
+
|
|
53
|
+
model_config = ConfigDict(extra="forbid")
|
|
54
|
+
|
|
55
|
+
max_attempts: int = Field(3, ge=1, le=10, description="Maximum number of retry attempts")
|
|
56
|
+
initial_delay: float = Field(1.0, ge=0, description="Initial delay in seconds")
|
|
57
|
+
max_delay: float = Field(60.0, ge=0, description="Maximum delay in seconds")
|
|
58
|
+
exponential_base: float = Field(2.0, ge=1.0, description="Exponential backoff base")
|
|
59
|
+
retry_on: list[str] | None = Field(None, description="Exception types to retry on")
|
|
60
|
+
not_retry_on: list[str] | None = Field(None, description="Exception types not to retry on")
|
|
61
|
+
|
|
62
|
+
@field_validator("max_delay")
|
|
63
|
+
@classmethod
|
|
64
|
+
def max_delay_greater_than_initial(cls, v: float, info) -> float:
|
|
65
|
+
"""Validate max_delay is greater than initial_delay."""
|
|
66
|
+
if "initial_delay" in info.data and v < info.data["initial_delay"]:
|
|
67
|
+
raise ValueError("max_delay must be greater than or equal to initial_delay")
|
|
68
|
+
return v
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class StepConfig(BaseModel):
|
|
72
|
+
"""Configuration for pipeline steps."""
|
|
73
|
+
|
|
74
|
+
model_config = ConfigDict(extra="forbid")
|
|
75
|
+
|
|
76
|
+
name: str = Field(..., min_length=1, description="Step name")
|
|
77
|
+
inputs: list[str] = Field(default_factory=list, description="Input asset names")
|
|
78
|
+
outputs: list[str] = Field(default_factory=list, description="Output asset names")
|
|
79
|
+
cache: CacheStrategy | bool = Field(True, description="Caching strategy")
|
|
80
|
+
retry: RetryConfig | None = Field(None, description="Retry configuration")
|
|
81
|
+
timeout: int | None = Field(None, ge=1, description="Timeout in seconds")
|
|
82
|
+
resources: ResourceRequirements | None = Field(None, description="Resource requirements")
|
|
83
|
+
tags: dict[str, str] = Field(default_factory=dict, description="Step tags")
|
|
84
|
+
enable_cache: bool = Field(True, description="Enable caching for this step")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PipelineConfig(BaseModel):
|
|
88
|
+
"""Configuration for pipelines."""
|
|
89
|
+
|
|
90
|
+
model_config = ConfigDict(extra="allow") # Allow extra fields for flexibility
|
|
91
|
+
|
|
92
|
+
name: str = Field(..., min_length=1, description="Pipeline name")
|
|
93
|
+
description: str | None = Field(None, description="Pipeline description")
|
|
94
|
+
version: str | None = Field(None, description="Pipeline version")
|
|
95
|
+
tags: dict[str, str] = Field(default_factory=dict, description="Pipeline tags")
|
|
96
|
+
enable_cache: bool = Field(True, description="Enable caching globally")
|
|
97
|
+
fail_fast: bool = Field(True, description="Stop on first error")
|
|
98
|
+
max_parallel: int | None = Field(None, ge=1, description="Maximum parallel steps")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ContextConfig(BaseModel):
|
|
102
|
+
"""Configuration for pipeline context."""
|
|
103
|
+
|
|
104
|
+
model_config = ConfigDict(extra="allow") # Allow arbitrary parameters
|
|
105
|
+
|
|
106
|
+
# Common ML parameters
|
|
107
|
+
learning_rate: float | None = Field(None, gt=0, lt=1, description="Learning rate")
|
|
108
|
+
batch_size: int | None = Field(None, ge=1, description="Batch size")
|
|
109
|
+
epochs: int | None = Field(None, ge=1, description="Number of epochs")
|
|
110
|
+
seed: int | None = Field(None, description="Random seed")
|
|
111
|
+
|
|
112
|
+
# Infrastructure parameters
|
|
113
|
+
device: str | None = Field(None, description="Device (cpu/cuda/mps)")
|
|
114
|
+
num_workers: int | None = Field(None, ge=0, description="Number of workers")
|
|
115
|
+
|
|
116
|
+
# Experiment metadata
|
|
117
|
+
experiment_name: str | None = Field(None, description="Experiment name")
|
|
118
|
+
run_name: str | None = Field(None, description="Run name")
|
|
119
|
+
tags: dict[str, Any] = Field(default_factory=dict, description="Context tags")
|
|
120
|
+
|
|
121
|
+
@field_validator("device")
|
|
122
|
+
@classmethod
|
|
123
|
+
def validate_device(cls, v: str | None) -> str | None:
|
|
124
|
+
"""Validate device string."""
|
|
125
|
+
if v is None:
|
|
126
|
+
return v
|
|
127
|
+
|
|
128
|
+
valid_devices = ["cpu", "cuda", "mps", "tpu"]
|
|
129
|
+
if v.lower() not in valid_devices and not v.startswith("cuda:"):
|
|
130
|
+
raise ValueError(f"Invalid device. Must be one of: {', '.join(valid_devices)} or 'cuda:N'")
|
|
131
|
+
|
|
132
|
+
return v.lower()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class StackConfig(BaseModel):
|
|
136
|
+
"""Configuration for execution stacks."""
|
|
137
|
+
|
|
138
|
+
model_config = ConfigDict(extra="forbid")
|
|
139
|
+
|
|
140
|
+
name: str = Field(..., min_length=1, description="Stack name")
|
|
141
|
+
executor_type: Literal["local", "aws", "gcp", "azure", "kubernetes"] = Field(
|
|
142
|
+
"local",
|
|
143
|
+
description="Executor type",
|
|
144
|
+
)
|
|
145
|
+
artifact_store_path: str = Field(".flowyml/artifacts", description="Artifact storage path")
|
|
146
|
+
metadata_store_path: str = Field(".flowyml/metadata.db", description="Metadata storage path")
|
|
147
|
+
container_registry: str | None = Field(None, description="Container registry URL")
|
|
148
|
+
enable_caching: bool = Field(True, description="Enable caching")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class DatasetSchema(BaseModel):
|
|
152
|
+
"""Schema for dataset validation."""
|
|
153
|
+
|
|
154
|
+
model_config = ConfigDict(extra="allow")
|
|
155
|
+
|
|
156
|
+
name: str = Field(..., description="Dataset name")
|
|
157
|
+
version: str | None = Field(None, description="Dataset version")
|
|
158
|
+
num_samples: int | None = Field(None, ge=0, description="Number of samples")
|
|
159
|
+
num_features: int | None = Field(None, ge=0, description="Number of features")
|
|
160
|
+
feature_names: list[str] | None = Field(None, description="Feature names")
|
|
161
|
+
target_column: str | None = Field(None, description="Target column name")
|
|
162
|
+
task_type: Literal["classification", "regression", "clustering", "other"] | None = Field(
|
|
163
|
+
None,
|
|
164
|
+
description="ML task type",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ModelSchema(BaseModel):
|
|
169
|
+
"""Schema for model validation."""
|
|
170
|
+
|
|
171
|
+
model_config = ConfigDict(extra="allow")
|
|
172
|
+
|
|
173
|
+
name: str = Field(..., description="Model name")
|
|
174
|
+
framework: Literal["pytorch", "tensorflow", "sklearn", "xgboost", "lightgbm", "other"] = Field(
|
|
175
|
+
...,
|
|
176
|
+
description="ML framework",
|
|
177
|
+
)
|
|
178
|
+
architecture: str | None = Field(None, description="Model architecture")
|
|
179
|
+
input_shape: list[int] | None = Field(None, description="Input shape")
|
|
180
|
+
output_shape: list[int] | None = Field(None, description="Output shape")
|
|
181
|
+
num_parameters: int | None = Field(None, ge=0, description="Number of parameters")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class MetricsSchema(BaseModel):
|
|
185
|
+
"""Schema for metrics validation."""
|
|
186
|
+
|
|
187
|
+
model_config = ConfigDict(extra="allow")
|
|
188
|
+
|
|
189
|
+
accuracy: float | None = Field(None, ge=0, le=1, description="Accuracy")
|
|
190
|
+
precision: float | None = Field(None, ge=0, le=1, description="Precision")
|
|
191
|
+
recall: float | None = Field(None, ge=0, le=1, description="Recall")
|
|
192
|
+
f1_score: float | None = Field(None, ge=0, le=1, description="F1 score")
|
|
193
|
+
loss: float | None = Field(None, ge=0, description="Loss")
|
|
194
|
+
mse: float | None = Field(None, ge=0, description="Mean squared error")
|
|
195
|
+
mae: float | None = Field(None, ge=0, description="Mean absolute error")
|
|
196
|
+
r2_score: float | None = Field(None, description="R2 score")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ExperimentConfig(BaseModel):
|
|
200
|
+
"""Configuration for experiments."""
|
|
201
|
+
|
|
202
|
+
model_config = ConfigDict(extra="allow")
|
|
203
|
+
|
|
204
|
+
name: str = Field(..., description="Experiment name")
|
|
205
|
+
description: str | None = Field(None, description="Experiment description")
|
|
206
|
+
tags: dict[str, str] = Field(default_factory=dict, description="Experiment tags")
|
|
207
|
+
parameters: dict[str, Any] = Field(default_factory=dict, description="Experiment parameters")
|
|
208
|
+
track_code: bool = Field(True, description="Track code changes")
|
|
209
|
+
track_environment: bool = Field(True, description="Track environment")
|
|
210
|
+
track_system: bool = Field(True, description="Track system info")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def validate_step_config(config: dict[str, Any]) -> StepConfig:
|
|
214
|
+
"""Validate step configuration.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
config: Step configuration dictionary
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Validated StepConfig instance
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
ValidationError: If validation fails
|
|
224
|
+
"""
|
|
225
|
+
return StepConfig(**config)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def validate_pipeline_config(config: dict[str, Any]) -> PipelineConfig:
|
|
229
|
+
"""Validate pipeline configuration.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
config: Pipeline configuration dictionary
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Validated PipelineConfig instance
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
ValidationError: If validation fails
|
|
239
|
+
"""
|
|
240
|
+
return PipelineConfig(**config)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def validate_context_config(config: dict[str, Any]) -> ContextConfig:
|
|
244
|
+
"""Validate context configuration.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
config: Context configuration dictionary
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Validated ContextConfig instance
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ValidationError: If validation fails
|
|
254
|
+
"""
|
|
255
|
+
return ContextConfig(**config)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def validate_metrics(metrics: dict[str, Any]) -> MetricsSchema:
|
|
259
|
+
"""Validate metrics.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
metrics: Metrics dictionary
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Validated MetricsSchema instance
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
ValidationError: If validation fails
|
|
269
|
+
"""
|
|
270
|
+
return MetricsSchema(**metrics)
|