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,31 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ darkMode: 'class',
8
+ theme: {
9
+ extend: {
10
+ colors: {
11
+ primary: {
12
+ 50: '#f0f9ff',
13
+ 100: '#e0f2fe',
14
+ 200: '#bae6fd',
15
+ 300: '#7dd3fc',
16
+ 400: '#38bdf8',
17
+ 500: '#0ea5e9',
18
+ 600: '#0284c7',
19
+ 700: '#0369a1',
20
+ 800: '#075985',
21
+ 900: '#0c4a6e',
22
+ 950: '#082f49',
23
+ }
24
+ },
25
+ fontFamily: {
26
+ sans: ['Inter', 'system-ui', 'sans-serif'],
27
+ }
28
+ },
29
+ },
30
+ plugins: [],
31
+ }
@@ -0,0 +1,21 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import path from 'path'
4
+
5
+ // https://vitejs.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ resolve: {
9
+ alias: {
10
+ "@": path.resolve(__dirname, "./src"),
11
+ },
12
+ },
13
+ server: {
14
+ proxy: {
15
+ '/api': {
16
+ target: 'http://localhost:8080',
17
+ changeOrigin: true,
18
+ },
19
+ },
20
+ },
21
+ })
flowyml/ui/utils.py ADDED
@@ -0,0 +1,77 @@
1
+ """UI utility functions for checking UI server status and getting URLs."""
2
+
3
+ import http.client
4
+
5
+
6
+ def is_ui_running(host: str = "localhost", port: int = 8080) -> bool:
7
+ """Check if the flowyml UI server is running.
8
+
9
+ Args:
10
+ host: Host to check (default: localhost)
11
+ port: Port to check (default: 8080)
12
+
13
+ Returns:
14
+ True if UI server is running and responding, False otherwise
15
+ """
16
+ try:
17
+ conn = http.client.HTTPConnection(host, port, timeout=2)
18
+ conn.request("GET", "/api/health")
19
+ response = conn.getresponse()
20
+ conn.close()
21
+
22
+ # Check if response is successful and from flowyml
23
+ if response.status == 200:
24
+ data = response.read().decode("utf-8")
25
+ return "flowyml" in data.lower() or "ok" in data.lower()
26
+ return False
27
+ except Exception:
28
+ return False
29
+
30
+
31
+ def get_ui_url(host: str = "localhost", port: int = 8080) -> str | None:
32
+ """Get the URL of the running flowyml UI server.
33
+
34
+ Args:
35
+ host: Host of the UI server (default: localhost)
36
+ port: Port of the UI server (default: 8080)
37
+
38
+ Returns:
39
+ URL string if server is running, None otherwise
40
+ """
41
+ if is_ui_running(host, port):
42
+ return f"http://{host}:{port}"
43
+ return None
44
+
45
+
46
+ def get_run_url(run_id: str, host: str = "localhost", port: int = 8080) -> str | None:
47
+ """Get the URL to view a specific pipeline run.
48
+
49
+ Args:
50
+ run_id: ID of the pipeline run
51
+ host: Host of the UI server (default: localhost)
52
+ port: Port of the UI server (default: 8080)
53
+
54
+ Returns:
55
+ URL string to the run view if server is running, None otherwise
56
+ """
57
+ base_url = get_ui_url(host, port)
58
+ if base_url:
59
+ return f"{base_url}/runs/{run_id}"
60
+ return None
61
+
62
+
63
+ def get_pipeline_url(pipeline_name: str, host: str = "localhost", port: int = 8080) -> str | None:
64
+ """Get the URL to view a specific pipeline.
65
+
66
+ Args:
67
+ pipeline_name: Name of the pipeline
68
+ host: Host of the UI server (default: localhost)
69
+ port: Port of the UI server (default: 8080)
70
+
71
+ Returns:
72
+ URL string to the pipeline view if server is running, None otherwise
73
+ """
74
+ base_url = get_ui_url(host, port)
75
+ if base_url:
76
+ return f"{base_url}/pipelines/{pipeline_name}"
77
+ return None
@@ -0,0 +1,67 @@
1
+ """Utility functions and helpers."""
2
+
3
+ from flowyml.utils.logging import setup_logger, get_logger
4
+ from flowyml.utils.config import (
5
+ get_config,
6
+ set_config,
7
+ reset_config,
8
+ update_config,
9
+ load_project_config,
10
+ save_project_config,
11
+ )
12
+ from flowyml.utils.validation import (
13
+ CacheStrategy,
14
+ ResourceRequirements,
15
+ RetryConfig,
16
+ StepConfig,
17
+ PipelineConfig,
18
+ ContextConfig,
19
+ StackConfig,
20
+ DatasetSchema,
21
+ ModelSchema,
22
+ MetricsSchema,
23
+ ExperimentConfig,
24
+ validate_step_config,
25
+ validate_pipeline_config,
26
+ validate_context_config,
27
+ validate_metrics,
28
+ )
29
+ from flowyml.utils.git import GitInfo, get_git_info, is_git_repo
30
+ from flowyml.utils.environment import capture_environment, save_environment
31
+
32
+ __all__ = [
33
+ # Logging
34
+ "setup_logger",
35
+ "get_logger",
36
+ # Configuration
37
+ "flowymlConfig",
38
+ "get_config",
39
+ "set_config",
40
+ "reset_config",
41
+ "update_config",
42
+ "load_project_config",
43
+ "save_project_config",
44
+ # Validation
45
+ "CacheStrategy",
46
+ "ResourceRequirements",
47
+ "RetryConfig",
48
+ "StepConfig",
49
+ "PipelineConfig",
50
+ "ContextConfig",
51
+ "StackConfig",
52
+ "DatasetSchema",
53
+ "ModelSchema",
54
+ "MetricsSchema",
55
+ "ExperimentConfig",
56
+ "validate_step_config",
57
+ "validate_pipeline_config",
58
+ "validate_context_config",
59
+ "validate_metrics",
60
+ # Git
61
+ "GitInfo",
62
+ "get_git_info",
63
+ "is_git_repo",
64
+ # Environment
65
+ "capture_environment",
66
+ "save_environment",
67
+ ]
@@ -0,0 +1,308 @@
1
+ """Configuration management for flowyml."""
2
+
3
+ import os
4
+ import yaml
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ @dataclass
11
+ class FlowymlConfig:
12
+ """Global flowyml configuration."""
13
+
14
+ # Storage paths
15
+ flowyml_home: Path = field(default_factory=lambda: Path.home() / ".flowyml")
16
+ artifacts_dir: Path = field(default_factory=lambda: Path(".flowyml/artifacts"))
17
+ metadata_db: Path = field(default_factory=lambda: Path(".flowyml/metadata.db"))
18
+ cache_dir: Path = field(default_factory=lambda: Path(".flowyml/cache"))
19
+ runs_dir: Path = field(default_factory=lambda: Path(".flowyml/runs"))
20
+ experiments_dir: Path = field(default_factory=lambda: Path(".flowyml/experiments"))
21
+
22
+ # Execution settings
23
+ default_stack: str = "local"
24
+ execution_mode: str = "local" # local or remote
25
+ remote_server_url: str = ""
26
+ remote_ui_url: str = ""
27
+ enable_caching: bool = True
28
+ enable_logging: bool = True
29
+ log_level: str = "INFO"
30
+ max_cache_size_mb: int = 10000 # 10GB default
31
+
32
+ # UI settings
33
+ ui_host: str = "localhost"
34
+ ui_port: int = 8080
35
+ enable_ui: bool = False
36
+
37
+ # Experiment tracking
38
+ auto_log_params: bool = True
39
+ auto_log_metrics: bool = True
40
+ auto_log_artifacts: bool = True
41
+ track_git: bool = True
42
+ track_environment: bool = True
43
+
44
+ # Performance settings
45
+ max_parallel_steps: int = 4
46
+ step_timeout_seconds: int = 3600 # 1 hour default
47
+ retry_max_attempts: int = 3
48
+
49
+ # Advanced settings
50
+ debug_mode: bool = False
51
+ strict_validation: bool = True
52
+ allow_pickle: bool = True
53
+
54
+ def __post_init__(self):
55
+ """Ensure all paths are Path objects."""
56
+ for field_name in [
57
+ "flowyml_home",
58
+ "artifacts_dir",
59
+ "metadata_db",
60
+ "cache_dir",
61
+ "runs_dir",
62
+ "experiments_dir",
63
+ ]:
64
+ value = getattr(self, field_name)
65
+ if not isinstance(value, Path):
66
+ setattr(self, field_name, Path(value))
67
+
68
+ def create_directories(self) -> None:
69
+ """Create necessary directories."""
70
+ self.flowyml_home.mkdir(parents=True, exist_ok=True)
71
+ self.artifacts_dir.mkdir(parents=True, exist_ok=True)
72
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
73
+ self.runs_dir.mkdir(parents=True, exist_ok=True)
74
+ self.experiments_dir.mkdir(parents=True, exist_ok=True)
75
+
76
+ # Create metadata db parent dir
77
+ self.metadata_db.parent.mkdir(parents=True, exist_ok=True)
78
+
79
+ def to_dict(self) -> dict[str, Any]:
80
+ """Convert config to dictionary."""
81
+ return {
82
+ "flowyml_home": str(self.flowyml_home),
83
+ "artifacts_dir": str(self.artifacts_dir),
84
+ "metadata_db": str(self.metadata_db),
85
+ "cache_dir": str(self.cache_dir),
86
+ "runs_dir": str(self.runs_dir),
87
+ "experiments_dir": str(self.experiments_dir),
88
+ "default_stack": self.default_stack,
89
+ "execution_mode": self.execution_mode,
90
+ "remote_server_url": self.remote_server_url,
91
+ "remote_ui_url": self.remote_ui_url,
92
+ "enable_caching": self.enable_caching,
93
+ "enable_logging": self.enable_logging,
94
+ "log_level": self.log_level,
95
+ "max_cache_size_mb": self.max_cache_size_mb,
96
+ "ui_host": self.ui_host,
97
+ "ui_port": self.ui_port,
98
+ "enable_ui": self.enable_ui,
99
+ "auto_log_params": self.auto_log_params,
100
+ "auto_log_metrics": self.auto_log_metrics,
101
+ "auto_log_artifacts": self.auto_log_artifacts,
102
+ "track_git": self.track_git,
103
+ "track_environment": self.track_environment,
104
+ "max_parallel_steps": self.max_parallel_steps,
105
+ "step_timeout_seconds": self.step_timeout_seconds,
106
+ "retry_max_attempts": self.retry_max_attempts,
107
+ "debug_mode": self.debug_mode,
108
+ "strict_validation": self.strict_validation,
109
+ "allow_pickle": self.allow_pickle,
110
+ }
111
+
112
+ @classmethod
113
+ def from_dict(cls, data: dict[str, Any]) -> "FlowymlConfig":
114
+ """Create config from dictionary."""
115
+ return cls(**data)
116
+
117
+ def save(self, path: Path | None = None) -> None:
118
+ """Save config to file.
119
+
120
+ Args:
121
+ path: Path to save config (defaults to ~/.flowyml/config.yaml)
122
+ """
123
+ if path is None:
124
+ path = self.flowyml_home / "config.yaml"
125
+
126
+ path.parent.mkdir(parents=True, exist_ok=True)
127
+
128
+ with open(path, "w") as f:
129
+ yaml.dump(self.to_dict(), f, default_flow_style=False)
130
+
131
+ @classmethod
132
+ def load(cls, path: Path | None = None) -> "FlowymlConfig":
133
+ """Load config from file.
134
+
135
+ Args:
136
+ path: Path to load config from (defaults to ~/.flowyml/config.yaml)
137
+
138
+ Returns:
139
+ Loaded FlowymlConfig instance
140
+ """
141
+ if path is None:
142
+ path = Path.home() / ".flowyml" / "config.yaml"
143
+
144
+ if not path.exists():
145
+ # Return default config
146
+ return cls()
147
+
148
+ with open(path) as f:
149
+ data = yaml.safe_load(f)
150
+
151
+ return cls.from_dict(data)
152
+
153
+
154
+ # Global config instance
155
+ _global_config: FlowymlConfig | None = None
156
+
157
+
158
+ def get_config() -> FlowymlConfig:
159
+ """Get global flowyml configuration.
160
+
161
+ Returns:
162
+ Global FlowymlConfig instance
163
+ """
164
+ global _global_config
165
+
166
+ if _global_config is None:
167
+ # Try to load from environment variable
168
+ config_path = os.environ.get("FLOWYML_CONFIG")
169
+ if config_path:
170
+ _global_config = FlowymlConfig.load(Path(config_path))
171
+ else:
172
+ # Load from default location
173
+ _global_config = FlowymlConfig.load()
174
+
175
+ # Create necessary directories
176
+ _global_config.create_directories()
177
+
178
+ return _global_config
179
+
180
+
181
+ def set_config(config: FlowymlConfig) -> None:
182
+ """Set global flowyml configuration.
183
+
184
+ Args:
185
+ config: FlowymlConfig instance to set as global
186
+ """
187
+ global _global_config
188
+ _global_config = config
189
+ _global_config.create_directories()
190
+
191
+
192
+ def reset_config() -> None:
193
+ """Reset global configuration to defaults."""
194
+ global _global_config
195
+ _global_config = FlowymlConfig()
196
+ _global_config.create_directories()
197
+
198
+
199
+ def update_config(**kwargs) -> None:
200
+ """Update global configuration with new values.
201
+
202
+ Args:
203
+ **kwargs: Configuration values to update
204
+ """
205
+ config = get_config()
206
+ for key, value in kwargs.items():
207
+ if hasattr(config, key):
208
+ setattr(config, key, value)
209
+
210
+
211
+ def load_project_config(project_dir: Path | None = None) -> dict[str, Any]:
212
+ """Load project-specific configuration.
213
+
214
+ Args:
215
+ project_dir: Project directory (defaults to current directory)
216
+
217
+ Returns:
218
+ Project configuration dictionary
219
+ """
220
+ if project_dir is None:
221
+ project_dir = Path.cwd()
222
+
223
+ config_path = project_dir / "flowyml.yaml"
224
+ if not config_path.exists():
225
+ config_path = project_dir / "flowyml.yml"
226
+
227
+ if not config_path.exists():
228
+ return {}
229
+
230
+ with open(config_path) as f:
231
+ return yaml.safe_load(f) or {}
232
+
233
+
234
+ def save_project_config(config: dict[str, Any], project_dir: Path | None = None) -> None:
235
+ """Save project-specific configuration.
236
+
237
+ Args:
238
+ config: Configuration dictionary
239
+ project_dir: Project directory (defaults to current directory)
240
+ """
241
+ if project_dir is None:
242
+ project_dir = Path.cwd()
243
+
244
+ config_path = project_dir / "flowyml.yaml"
245
+
246
+ with open(config_path, "w") as f:
247
+ yaml.dump(config, f, default_flow_style=False)
248
+
249
+
250
+ # Environment variable helpers
251
+
252
+
253
+ def get_env_config() -> dict[str, Any]:
254
+ """Get configuration from environment variables.
255
+
256
+ Returns:
257
+ Dictionary of configuration values from environment
258
+ """
259
+ env_config = {}
260
+
261
+ # Map environment variables to config fields
262
+ env_mappings = {
263
+ "flowyml_HOME": "flowyml_home",
264
+ "flowyml_ARTIFACTS_DIR": "artifacts_dir",
265
+ "flowyml_METADATA_DB": "metadata_db",
266
+ "flowyml_CACHE_DIR": "cache_dir",
267
+ "flowyml_DEFAULT_STACK": "default_stack",
268
+ "flowyml_EXECUTION_MODE": "execution_mode",
269
+ "flowyml_REMOTE_SERVER_URL": "remote_server_url",
270
+ "flowyml_REMOTE_UI_URL": "remote_ui_url",
271
+ "flowyml_ENABLE_CACHING": "enable_caching",
272
+ "flowyml_LOG_LEVEL": "log_level",
273
+ "flowyml_UI_HOST": "ui_host",
274
+ "flowyml_UI_PORT": "ui_port",
275
+ "flowyml_DEBUG": "debug_mode",
276
+ }
277
+
278
+ for env_var, config_key in env_mappings.items():
279
+ value = os.environ.get(env_var)
280
+ if value is not None:
281
+ # Convert booleans
282
+ if value.lower() in ("true", "1", "yes"):
283
+ value = True
284
+ elif value.lower() in ("false", "0", "no"):
285
+ value = False
286
+ # Convert integers
287
+ elif value.isdigit():
288
+ value = int(value)
289
+
290
+ env_config[config_key] = value
291
+
292
+ return env_config
293
+
294
+
295
+ def init_config_from_env() -> FlowymlConfig:
296
+ """Initialize configuration from environment variables.
297
+
298
+ Returns:
299
+ FlowymlConfig instance with values from environment
300
+ """
301
+ env_config = get_env_config()
302
+ config = FlowymlConfig()
303
+
304
+ for key, value in env_config.items():
305
+ if hasattr(config, key):
306
+ setattr(config, key, value)
307
+
308
+ return config