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,419 @@
1
+ """Error handling utilities for robust pipeline execution."""
2
+
3
+ import time
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
6
+ from typing import Any
7
+ from collections.abc import Callable
8
+ from enum import Enum
9
+
10
+
11
+ class CircuitState(Enum):
12
+ """Circuit breaker states."""
13
+
14
+ CLOSED = "closed" # Normal operation
15
+ OPEN = "open" # Failures exceed threshold, rejecting requests
16
+ HALF_OPEN = "half_open" # Testing if service recovered
17
+
18
+
19
+ @dataclass
20
+ class CircuitBreakerConfig:
21
+ """Configuration for circuit breaker."""
22
+
23
+ failure_threshold: int = 5
24
+ """Number of failures before opening circuit"""
25
+
26
+ timeout: float = 60
27
+ """Time to wait before trying again (seconds)"""
28
+
29
+ recovery_timeout: float = 300
30
+ """Time to wait before fully closing circuit (seconds)"""
31
+
32
+ expected_exceptions: list[type[Exception]] = field(default_factory=lambda: [Exception])
33
+ """Exceptions that trigger circuit breaker"""
34
+
35
+
36
+ class CircuitBreaker:
37
+ """Circuit breaker pattern implementation.
38
+
39
+ Prevents cascading failures by failing fast when a service is down.
40
+
41
+ Example:
42
+ ```python
43
+ @step(circuit_breaker=CircuitBreaker(failure_threshold=3, timeout=60))
44
+ def call_api(url):
45
+ return requests.get(url).json()
46
+ ```
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ failure_threshold: int = 5,
52
+ timeout: float = 60,
53
+ recovery_timeout: float = 300,
54
+ expected_exceptions: list[type[Exception]] | None = None,
55
+ ):
56
+ """Initialize circuit breaker.
57
+
58
+ Args:
59
+ failure_threshold: Number of failures before opening circuit
60
+ timeout: Seconds to wait before trying again
61
+ recovery_timeout: Seconds to wait before fully closing circuit
62
+ expected_exceptions: Exceptions that trigger the breaker
63
+ """
64
+ self.config = CircuitBreakerConfig(
65
+ failure_threshold=failure_threshold,
66
+ timeout=timeout,
67
+ recovery_timeout=recovery_timeout,
68
+ expected_exceptions=expected_exceptions or [Exception],
69
+ )
70
+
71
+ self.state = CircuitState.CLOSED
72
+ self.failure_count = 0
73
+ self.last_failure_time: datetime | None = None
74
+ self.success_count = 0
75
+
76
+ def call(self, func: Callable, *args, **kwargs) -> Any:
77
+ """Call function with circuit breaker protection.
78
+
79
+ Args:
80
+ func: Function to call
81
+ *args: Positional arguments
82
+ **kwargs: Keyword arguments
83
+
84
+ Returns:
85
+ Function result
86
+
87
+ Raises:
88
+ CircuitOpenError: If circuit is open
89
+ """
90
+ if self.state == CircuitState.OPEN:
91
+ if self._should_attempt_reset():
92
+ self.state = CircuitState.HALF_OPEN
93
+ else:
94
+ raise CircuitOpenError(
95
+ f"Circuit breaker is open. Wait {self.config.timeout}s before retry.",
96
+ )
97
+
98
+ try:
99
+ result = func(*args, **kwargs)
100
+ self._on_success()
101
+ return result
102
+
103
+ except Exception as e:
104
+ if self._is_expected_exception(e):
105
+ self._on_failure()
106
+ raise
107
+
108
+ def _should_attempt_reset(self) -> bool:
109
+ """Check if enough time has passed to attempt reset."""
110
+ if self.last_failure_time is None:
111
+ return True
112
+
113
+ time_since_failure = (datetime.now() - self.last_failure_time).total_seconds()
114
+ return time_since_failure >= self.config.timeout
115
+
116
+ def _is_expected_exception(self, exception: Exception) -> bool:
117
+ """Check if exception should trigger circuit breaker."""
118
+ return any(isinstance(exception, exc_type) for exc_type in self.config.expected_exceptions)
119
+
120
+ def _on_success(self) -> None:
121
+ """Handle successful call."""
122
+ self.failure_count = 0
123
+
124
+ if self.state == CircuitState.HALF_OPEN:
125
+ self.success_count += 1
126
+ # Fully close circuit after successful recovery period
127
+ if (
128
+ self.last_failure_time
129
+ and (datetime.now() - self.last_failure_time).total_seconds() >= self.config.recovery_timeout
130
+ ):
131
+ self.state = CircuitState.CLOSED
132
+ self.success_count = 0
133
+
134
+ def _on_failure(self) -> None:
135
+ """Handle failed call."""
136
+ self.failure_count += 1
137
+ self.last_failure_time = datetime.now()
138
+
139
+ if self.failure_count >= self.config.failure_threshold:
140
+ self.state = CircuitState.OPEN
141
+
142
+ def reset(self) -> None:
143
+ """Reset circuit breaker to closed state."""
144
+ self.state = CircuitState.CLOSED
145
+ self.failure_count = 0
146
+ self.success_count = 0
147
+ self.last_failure_time = None
148
+
149
+
150
+ class CircuitOpenError(Exception):
151
+ """Exception raised when circuit breaker is open."""
152
+
153
+ pass
154
+
155
+
156
+ @dataclass
157
+ class FallbackConfig:
158
+ """Configuration for fallback handler."""
159
+
160
+ fallback_func: Callable
161
+ """Fallback function to call on error"""
162
+
163
+ fallback_on: list[type[Exception]] = field(default_factory=lambda: [Exception])
164
+ """Exceptions that trigger fallback"""
165
+
166
+ max_fallback_attempts: int = 1
167
+ """Maximum number of fallback attempts"""
168
+
169
+
170
+ class FallbackHandler:
171
+ """Fallback handler for graceful degradation.
172
+
173
+ Example:
174
+ ```python
175
+ @step(fallback=lambda: load_cached_data(), fallback_on=[TimeoutError])
176
+ def fetch_live_data():
177
+ return requests.get(url).json()
178
+ ```
179
+ """
180
+
181
+ def __init__(
182
+ self,
183
+ fallback_func: Callable,
184
+ fallback_on: list[type[Exception]] | None = None,
185
+ max_attempts: int = 1,
186
+ ):
187
+ """Initialize fallback handler.
188
+
189
+ Args:
190
+ fallback_func: Function to call as fallback
191
+ fallback_on: Exceptions that trigger fallback
192
+ max_attempts: Maximum fallback attempts
193
+ """
194
+ self.config = FallbackConfig(
195
+ fallback_func=fallback_func,
196
+ fallback_on=fallback_on or [Exception],
197
+ max_fallback_attempts=max_attempts,
198
+ )
199
+ self.fallback_attempts = 0
200
+
201
+ def call(self, func: Callable, *args, **kwargs) -> Any:
202
+ """Call function with fallback protection.
203
+
204
+ Args:
205
+ func: Primary function to call
206
+ *args: Positional arguments
207
+ **kwargs: Keyword arguments
208
+
209
+ Returns:
210
+ Function result or fallback result
211
+ """
212
+ try:
213
+ return func(*args, **kwargs)
214
+
215
+ except Exception as e:
216
+ if self._should_fallback(e) and self.fallback_attempts < self.config.max_fallback_attempts:
217
+ self.fallback_attempts += 1
218
+ return self.config.fallback_func()
219
+ raise
220
+
221
+ def _should_fallback(self, exception: Exception) -> bool:
222
+ """Check if exception should trigger fallback."""
223
+ return any(isinstance(exception, exc_type) for exc_type in self.config.fallback_on)
224
+
225
+ def reset(self) -> None:
226
+ """Reset fallback attempts counter."""
227
+ self.fallback_attempts = 0
228
+
229
+
230
+ class ExponentialBackoff:
231
+ """Exponential backoff retry strategy.
232
+
233
+ Example:
234
+ ```python
235
+ from flowyml import step, retry, ExponentialBackoff
236
+
237
+
238
+ @step(
239
+ retry=retry(
240
+ max_attempts=5, backoff=ExponentialBackoff(initial=1, max=60, multiplier=2), on=[NetworkError, TimeoutError]
241
+ )
242
+ )
243
+ def fetch_data():
244
+ return api.get_data()
245
+ ```
246
+ """
247
+
248
+ def __init__(
249
+ self,
250
+ initial: float = 1.0,
251
+ max_delay: float = 60.0,
252
+ multiplier: float = 2.0,
253
+ jitter: bool = True,
254
+ ):
255
+ """Initialize exponential backoff.
256
+
257
+ Args:
258
+ initial: Initial delay in seconds
259
+ max_delay: Maximum delay in seconds
260
+ multiplier: Backoff multiplier
261
+ jitter: Add random jitter to delays
262
+ """
263
+ self.initial = initial
264
+ self.max_delay = max_delay
265
+ self.multiplier = multiplier
266
+ self.jitter = jitter
267
+ self.attempt = 0
268
+
269
+ def get_delay(self) -> float:
270
+ """Get delay for current attempt.
271
+
272
+ Returns:
273
+ Delay in seconds
274
+ """
275
+ delay = min(self.initial * (self.multiplier**self.attempt), self.max_delay)
276
+
277
+ if self.jitter:
278
+ import random
279
+
280
+ delay = delay * (0.5 + random.random())
281
+
282
+ self.attempt += 1
283
+ return delay
284
+
285
+ def reset(self) -> None:
286
+ """Reset attempt counter."""
287
+ self.attempt = 0
288
+
289
+
290
+ @dataclass
291
+ class RetryConfig:
292
+ """Configuration for retry logic."""
293
+
294
+ max_attempts: int = 3
295
+ """Maximum number of retry attempts"""
296
+
297
+ backoff: ExponentialBackoff | None = None
298
+ """Backoff strategy"""
299
+
300
+ retry_on: list[type[Exception]] = field(default_factory=lambda: [Exception])
301
+ """Exceptions to retry on"""
302
+
303
+ not_retry_on: list[type[Exception]] = field(default_factory=list)
304
+ """Exceptions NOT to retry on"""
305
+
306
+
307
+ def retry(
308
+ max_attempts: int = 3,
309
+ backoff: ExponentialBackoff | None = None,
310
+ on: list[type[Exception]] | None = None,
311
+ not_on: list[type[Exception]] | None = None,
312
+ ) -> RetryConfig:
313
+ """Create retry configuration.
314
+
315
+ Args:
316
+ max_attempts: Maximum retry attempts
317
+ backoff: Backoff strategy
318
+ on: Exceptions to retry on
319
+ not_on: Exceptions not to retry on
320
+
321
+ Returns:
322
+ RetryConfig instance
323
+ """
324
+ return RetryConfig(
325
+ max_attempts=max_attempts,
326
+ backoff=backoff or ExponentialBackoff(),
327
+ retry_on=on or [Exception],
328
+ not_retry_on=not_on or [],
329
+ )
330
+
331
+
332
+ def execute_with_retry(
333
+ func: Callable,
334
+ retry_config: RetryConfig,
335
+ *args,
336
+ **kwargs,
337
+ ) -> Any:
338
+ """Execute function with retry logic.
339
+
340
+ Args:
341
+ func: Function to execute
342
+ retry_config: Retry configuration
343
+ *args: Positional arguments
344
+ **kwargs: Keyword arguments
345
+
346
+ Returns:
347
+ Function result
348
+
349
+ Raises:
350
+ Last exception if all retries fail
351
+ """
352
+ last_exception = None
353
+
354
+ for attempt in range(retry_config.max_attempts):
355
+ try:
356
+ return func(*args, **kwargs)
357
+
358
+ except Exception as e:
359
+ # Don't retry if in not_retry_on list
360
+ if any(isinstance(e, exc_type) for exc_type in retry_config.not_retry_on):
361
+ raise
362
+
363
+ # Only retry if in retry_on list
364
+ if not any(isinstance(e, exc_type) for exc_type in retry_config.retry_on):
365
+ raise
366
+
367
+ last_exception = e
368
+
369
+ # Don't sleep on last attempt
370
+ if attempt < retry_config.max_attempts - 1 and retry_config.backoff:
371
+ delay = retry_config.backoff.get_delay()
372
+ time.sleep(delay)
373
+
374
+ # All retries failed
375
+ if last_exception:
376
+ raise last_exception
377
+ raise RuntimeError("Retry failed with no exception captured")
378
+
379
+
380
+ @dataclass
381
+ class OnFailureConfig:
382
+ """Configuration for failure handling."""
383
+
384
+ action: str = "log"
385
+ """Action to take (log, email, slack, webhook)"""
386
+
387
+ recipients: list[str] = field(default_factory=list)
388
+ """Recipients for notifications"""
389
+
390
+ include_logs: bool = True
391
+ """Include logs in notification"""
392
+
393
+ include_traceback: bool = True
394
+ """Include full traceback"""
395
+
396
+
397
+ def on_failure(
398
+ action: str = "log",
399
+ recipients: list[str] | None = None,
400
+ include_logs: bool = True,
401
+ include_traceback: bool = True,
402
+ ) -> OnFailureConfig:
403
+ """Create failure handling configuration.
404
+
405
+ Args:
406
+ action: Action to take on failure
407
+ recipients: Recipients for notifications
408
+ include_logs: Include logs in notification
409
+ include_traceback: Include full traceback
410
+
411
+ Returns:
412
+ OnFailureConfig instance
413
+ """
414
+ return OnFailureConfig(
415
+ action=action,
416
+ recipients=recipients or [],
417
+ include_logs=include_logs,
418
+ include_traceback=include_traceback,
419
+ )