flowyml 1.5.0__py3-none-any.whl → 1.6.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.
@@ -0,0 +1,95 @@
1
+ """Rich utilities for CLI commands."""
2
+
3
+ try:
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from rich.panel import Panel
7
+ from rich.text import Text
8
+ from rich import box
9
+ from rich.tree import Tree
10
+
11
+ RICH_AVAILABLE = True
12
+ except ImportError:
13
+ RICH_AVAILABLE = False
14
+ Console = None
15
+ Table = None
16
+ Panel = None
17
+ Text = None
18
+ box = None
19
+ Tree = None
20
+
21
+
22
+ def get_console() -> Console | None:
23
+ """Get Rich console instance if available."""
24
+ return Console() if RICH_AVAILABLE else None
25
+
26
+
27
+ def print_rich_table(title: str, headers: list[str], rows: list[list[str]], console: Console | None = None) -> None:
28
+ """Print a rich table with fallback to simple output."""
29
+ if not console:
30
+ console = get_console()
31
+
32
+ if RICH_AVAILABLE and console:
33
+ table = Table(
34
+ title=f"[bold cyan]{title}[/bold cyan]",
35
+ box=box.ROUNDED,
36
+ show_header=True,
37
+ header_style="bold cyan",
38
+ border_style="cyan",
39
+ )
40
+ for header in headers:
41
+ table.add_column(header, style="cyan")
42
+ for row in rows:
43
+ table.add_row(*row)
44
+ console.print(table)
45
+ else:
46
+ # Fallback to simple output
47
+ print(f"\n{title}")
48
+ print("=" * 70)
49
+ print(" | ".join(headers))
50
+ print("-" * 70)
51
+ for row in rows:
52
+ print(" | ".join(str(cell) for cell in row))
53
+ print()
54
+
55
+
56
+ def print_rich_panel(content: str, title: str = "", style: str = "cyan", console: Console | None = None) -> None:
57
+ """Print a rich panel with fallback to simple output."""
58
+ if not console:
59
+ console = get_console()
60
+
61
+ if RICH_AVAILABLE and console:
62
+ panel = Panel(
63
+ content,
64
+ title=title,
65
+ border_style=style,
66
+ box=box.ROUNDED,
67
+ )
68
+ console.print(panel)
69
+ else:
70
+ # Fallback to simple output
71
+ if title:
72
+ print(f"\n{title}")
73
+ print("=" * 70)
74
+ print(content)
75
+ print()
76
+
77
+
78
+ def print_rich_text(*parts: tuple[str, str], console: Console | None = None) -> None:
79
+ """Print rich text with styles, fallback to simple output.
80
+
81
+ Args:
82
+ *parts: Tuples of (text, style) to print
83
+ console: Optional console instance
84
+ """
85
+ if not console:
86
+ console = get_console()
87
+
88
+ if RICH_AVAILABLE and console and Text:
89
+ text_obj = Text()
90
+ for text, style in parts:
91
+ text_obj.append(text, style=style)
92
+ console.print(text_obj)
93
+ else:
94
+ # Fallback to simple output
95
+ print("".join(text for text, _ in parts))
@@ -49,12 +49,17 @@ class PipelineCheckpoint:
49
49
  # Update checkpoint metadata
50
50
  checkpoint_data = self.load() if self.checkpoint_file.exists() else {}
51
51
 
52
+ # Get existing completed steps (avoid duplicates)
53
+ completed_steps = checkpoint_data.get("completed_steps", [])
54
+ if step_name not in completed_steps:
55
+ completed_steps.append(step_name)
56
+
52
57
  checkpoint_data.update(
53
58
  {
54
59
  "run_id": self.run_id,
55
60
  "last_completed_step": step_name,
56
61
  "last_update": datetime.now().isoformat(),
57
- "completed_steps": checkpoint_data.get("completed_steps", []) + [step_name],
62
+ "completed_steps": completed_steps,
58
63
  "step_metadata": checkpoint_data.get("step_metadata", {}),
59
64
  },
60
65
  )
@@ -371,3 +371,107 @@ def unless(condition_func: Callable[[Any], bool]) -> Callable:
371
371
  return wrapper
372
372
 
373
373
  return decorator
374
+
375
+
376
+ class If:
377
+ """If-else conditional control flow for pipelines.
378
+
379
+ Supports both constructor and fluent API styles.
380
+
381
+ Example (constructor style):
382
+ ```python
383
+ from flowyml import Pipeline, step, If
384
+
385
+
386
+ @step(outputs=["accuracy"])
387
+ def evaluate_model():
388
+ return 0.95
389
+
390
+
391
+ @step
392
+ def deploy_model():
393
+ print("Deploying...")
394
+
395
+
396
+ @step
397
+ def retrain_model():
398
+ print("Retraining...")
399
+
400
+
401
+ pipeline = Pipeline("conditional_deploy")
402
+ pipeline.add_step(evaluate_model)
403
+ pipeline.add_control_flow(
404
+ If(
405
+ condition=lambda ctx: ctx.steps["evaluate_model"].outputs["accuracy"] > 0.9,
406
+ then_step=deploy_model,
407
+ else_step=retrain_model,
408
+ )
409
+ )
410
+ ```
411
+
412
+ Example (fluent style):
413
+ ```python
414
+ pipeline.add_control_flow(
415
+ If(condition=lambda ctx: ctx["accuracy"] > 0.95).then(deploy_to_prod).else_(notify_slack_failure)
416
+ )
417
+ ```
418
+ """
419
+
420
+ def __init__(
421
+ self,
422
+ condition: Callable[[Any], bool],
423
+ then_step: Callable | None = None,
424
+ else_step: Callable | None = None,
425
+ ):
426
+ """Initialize If condition.
427
+
428
+ Args:
429
+ condition: Function that takes a context object and returns bool
430
+ then_step: Step to execute if condition is True (optional, can use .then() instead)
431
+ else_step: Step to execute if condition is False (optional, can use .else_() instead)
432
+ """
433
+ self.condition = condition
434
+ self.then_step = then_step
435
+ self.else_step = else_step
436
+
437
+ def then(self, step: Callable) -> "If":
438
+ """Set the step to execute if condition is True (fluent API).
439
+
440
+ Args:
441
+ step: Step function to execute
442
+
443
+ Returns:
444
+ Self for method chaining
445
+ """
446
+ self.then_step = step
447
+ return self
448
+
449
+ def else_(self, step: Callable) -> "If":
450
+ """Set the step to execute if condition is False (fluent API).
451
+
452
+ Args:
453
+ step: Step function to execute
454
+
455
+ Returns:
456
+ Self for method chaining
457
+ """
458
+ self.else_step = step
459
+ return self
460
+
461
+ def evaluate(self, context: Any) -> Callable | None:
462
+ """Evaluate condition and return the step to execute.
463
+
464
+ Args:
465
+ context: Context object with step outputs and other data
466
+
467
+ Returns:
468
+ Step function to execute, or None if no step should execute
469
+ """
470
+ try:
471
+ if self.condition(context):
472
+ return self.then_step
473
+ else:
474
+ return self.else_step
475
+ except Exception:
476
+ # If condition evaluation fails, return else_step as fallback
477
+ return self.else_step