edda-framework 0.6.0__py3-none-any.whl → 0.8.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,200 @@
1
+ """
2
+ Theme configuration for Edda Workflow Viewer.
3
+
4
+ Provides centralized color palette definitions and helper functions
5
+ for light and dark mode support.
6
+ """
7
+
8
+ from typing import Any, cast
9
+
10
+ # Tailwind CSS compatible color palette
11
+ COLORS: dict[str, dict[str, Any]] = {
12
+ "light": {
13
+ # Background colors
14
+ "bg_page": "#FFFFFF",
15
+ "bg_surface": "#F8FAFC", # Slate 50
16
+ "bg_elevated": "#FFFFFF",
17
+ "border": "#E2E8F0", # Slate 200
18
+ # Text colors
19
+ "text_primary": "#0F172A", # Slate 900
20
+ "text_secondary": "#64748B", # Slate 500
21
+ "text_muted": "#94A3B8", # Slate 400
22
+ # Status colors (bg, stroke, text)
23
+ "completed": {"bg": "#ECFDF5", "stroke": "#10B981", "text": "#065F46"},
24
+ "running": {"bg": "#FEF3C7", "stroke": "#F59E0B", "text": "#92400E"},
25
+ "failed": {"bg": "#FEE2E2", "stroke": "#EF4444", "text": "#991B1B"},
26
+ "waiting_event": {"bg": "#DBEAFE", "stroke": "#3B82F6", "text": "#1E40AF"},
27
+ "waiting_timer": {"bg": "#E0F2FE", "stroke": "#0EA5E9", "text": "#075985"},
28
+ "cancelled": {"bg": "#FFEDD5", "stroke": "#F97316", "text": "#9A3412"},
29
+ "compensating": {"bg": "#F3E8FF", "stroke": "#A855F7", "text": "#6B21A8"},
30
+ "not_executed": {"bg": "#F1F5F9", "stroke": "#CBD5E1", "text": "#64748B"},
31
+ "event_received": {"bg": "#CFFAFE", "stroke": "#06B6D4", "text": "#0E7490"},
32
+ "compensation_failed": {"bg": "#FEE2E2", "stroke": "#B91C1C", "text": "#7F1D1D"},
33
+ # Mermaid diagram specific
34
+ "condition": {"bg": "#FEF3C7", "stroke": "#F59E0B"},
35
+ "loop": {"bg": "#FDF4FF", "stroke": "#D946EF"},
36
+ "match": {"bg": "#ECFDF5", "stroke": "#10B981"},
37
+ "merge": {"bg": "#FFFFFF", "stroke": "#E2E8F0"},
38
+ "edge_executed": "#10B981",
39
+ "edge_not_executed": "#CBD5E1",
40
+ "edge_compensation": "#A855F7",
41
+ },
42
+ "dark": {
43
+ # Background colors
44
+ "bg_page": "#0F172A", # Slate 900
45
+ "bg_surface": "#1E293B", # Slate 800
46
+ "bg_elevated": "#334155", # Slate 700
47
+ "border": "#475569", # Slate 600
48
+ # Text colors
49
+ "text_primary": "#F1F5F9", # Slate 100
50
+ "text_secondary": "#94A3B8", # Slate 400
51
+ "text_muted": "#64748B", # Slate 500
52
+ # Status colors (bg, stroke, text)
53
+ "completed": {"bg": "#064E3B", "stroke": "#34D399", "text": "#A7F3D0"},
54
+ "running": {"bg": "#78350F", "stroke": "#FBBF24", "text": "#FDE68A"},
55
+ "failed": {"bg": "#7F1D1D", "stroke": "#F87171", "text": "#FECACA"},
56
+ "waiting_event": {"bg": "#1E3A8A", "stroke": "#60A5FA", "text": "#BFDBFE"},
57
+ "waiting_timer": {"bg": "#0C4A6E", "stroke": "#38BDF8", "text": "#BAE6FD"},
58
+ "cancelled": {"bg": "#7C2D12", "stroke": "#FB923C", "text": "#FED7AA"},
59
+ "compensating": {"bg": "#581C87", "stroke": "#C084FC", "text": "#E9D5FF"},
60
+ "not_executed": {"bg": "#334155", "stroke": "#64748B", "text": "#94A3B8"},
61
+ "event_received": {"bg": "#164E63", "stroke": "#22D3EE", "text": "#A5F3FC"},
62
+ "compensation_failed": {"bg": "#7F1D1D", "stroke": "#FCA5A5", "text": "#FECACA"},
63
+ # Mermaid diagram specific
64
+ "condition": {"bg": "#78350F", "stroke": "#FBBF24"},
65
+ "loop": {"bg": "#4C1D95", "stroke": "#E879F9"},
66
+ "match": {"bg": "#064E3B", "stroke": "#34D399"},
67
+ "merge": {"bg": "#1E293B", "stroke": "#475569"},
68
+ "edge_executed": "#34D399",
69
+ "edge_not_executed": "#64748B",
70
+ "edge_compensation": "#C084FC",
71
+ },
72
+ }
73
+
74
+
75
+ def get_status_color(status: str, is_dark: bool) -> dict[str, str]:
76
+ """
77
+ Get color configuration for a workflow status.
78
+
79
+ Args:
80
+ status: Status name (e.g., "completed", "running", "failed")
81
+ is_dark: Whether dark mode is enabled
82
+
83
+ Returns:
84
+ Dictionary with 'bg', 'stroke', and 'text' colors
85
+ """
86
+ theme = "dark" if is_dark else "light"
87
+ status_key = status.lower().replace(" ", "_").replace("-", "_")
88
+
89
+ # Handle special status mappings
90
+ status_mapping = {
91
+ "waiting": "waiting_event",
92
+ "waiting_for_event": "waiting_event",
93
+ "waiting_for_timer": "waiting_timer",
94
+ "compensated": "compensating",
95
+ }
96
+ status_key = status_mapping.get(status_key, status_key)
97
+
98
+ return cast(dict[str, str], COLORS[theme].get(status_key, COLORS[theme]["not_executed"]))
99
+
100
+
101
+ def get_mermaid_style(status: str, is_dark: bool) -> str:
102
+ """
103
+ Get Mermaid style string for a status.
104
+
105
+ Args:
106
+ status: Status name
107
+ is_dark: Whether dark mode is enabled
108
+
109
+ Returns:
110
+ Mermaid style string (e.g., "fill:#ECFDF5,stroke:#10B981,stroke-width:2px")
111
+ """
112
+ colors = get_status_color(status, is_dark)
113
+ return f"fill:{colors['bg']},stroke:{colors['stroke']},stroke-width:2px"
114
+
115
+
116
+ def get_mermaid_node_style(node_type: str, is_dark: bool) -> str:
117
+ """
118
+ Get Mermaid style for structural nodes (condition, loop, merge).
119
+
120
+ Args:
121
+ node_type: Node type (e.g., "condition", "loop", "merge")
122
+ is_dark: Whether dark mode is enabled
123
+
124
+ Returns:
125
+ Mermaid style string
126
+ """
127
+ theme = "dark" if is_dark else "light"
128
+ colors = COLORS[theme].get(node_type, COLORS[theme]["merge"])
129
+ stroke_width = "1px" if node_type == "merge" else "2px"
130
+ return f"fill:{colors['bg']},stroke:{colors['stroke']},stroke-width:{stroke_width}"
131
+
132
+
133
+ def get_edge_color(edge_type: str, is_dark: bool) -> str:
134
+ """
135
+ Get edge (arrow) color for Mermaid diagrams.
136
+
137
+ Args:
138
+ edge_type: Edge type ("executed", "not_executed", "compensation")
139
+ is_dark: Whether dark mode is enabled
140
+
141
+ Returns:
142
+ Color hex code
143
+ """
144
+ theme = "dark" if is_dark else "light"
145
+ key = f"edge_{edge_type}"
146
+ return cast(str, COLORS[theme].get(key, COLORS[theme]["edge_not_executed"]))
147
+
148
+
149
+ # Tailwind CSS class mappings for UI components
150
+ # Note: Background colors are controlled via CSS in app.py for proper dark mode support
151
+ # NiceGUI adds 'dark' class to body, but Tailwind's dark: prefix expects it on html
152
+ TAILWIND_CLASSES = {
153
+ "card": "border", # Background handled by CSS
154
+ "card_hover": "", # Hover handled by CSS
155
+ "surface": "", # Background handled by CSS
156
+ "text_primary": "text-slate-900 dark:text-slate-100",
157
+ "text_secondary": "text-slate-500 dark:text-slate-400",
158
+ "text_muted": "text-slate-400 dark:text-slate-500",
159
+ "border": "border-slate-200 dark:border-slate-700",
160
+ "input": "border-slate-300 dark:border-slate-600", # Background handled by CSS
161
+ "code_block": "border", # Background handled by CSS
162
+ }
163
+
164
+ # Status badge Tailwind classes
165
+ STATUS_BADGE_CLASSES = {
166
+ "completed": ("bg-emerald-50 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300"),
167
+ "running": "bg-amber-50 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
168
+ "failed": "bg-red-50 text-red-700 dark:bg-red-900/50 dark:text-red-300",
169
+ "waiting_event": "bg-blue-50 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300",
170
+ "waiting_timer": "bg-sky-50 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300",
171
+ "cancelled": ("bg-orange-50 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300"),
172
+ "compensating": ("bg-purple-50 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300"),
173
+ "not_executed": ("bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400"),
174
+ }
175
+
176
+
177
+ def get_status_badge_classes(status: str) -> str:
178
+ """
179
+ Get Tailwind CSS classes for a status badge.
180
+
181
+ Args:
182
+ status: Status name
183
+
184
+ Returns:
185
+ Tailwind CSS class string
186
+ """
187
+ status_key = status.lower().replace(" ", "_").replace("-", "_")
188
+
189
+ # Handle special status mappings
190
+ status_mapping = {
191
+ "waiting": "waiting_event",
192
+ "waiting_for_event": "waiting_event",
193
+ "waiting_for_timer": "waiting_timer",
194
+ "compensated": "compensating",
195
+ }
196
+ status_key = status_mapping.get(status_key, status_key)
197
+
198
+ base_classes = "px-3 py-1 rounded-full text-sm font-medium"
199
+ status_classes = STATUS_BADGE_CLASSES.get(status_key, STATUS_BADGE_CLASSES["not_executed"])
200
+ return f"{base_classes} {status_classes}"
edda/workflow.py CHANGED
@@ -14,6 +14,49 @@ from edda.pydantic_utils import to_json_dict
14
14
 
15
15
  F = TypeVar("F", bound=Callable[..., Any])
16
16
 
17
+
18
+ class RecurException(Exception):
19
+ """
20
+ Exception raised to signal that a workflow should recur (restart with fresh history).
21
+
22
+ This is similar to Erlang's tail recursion pattern - it prevents unbounded history
23
+ growth in long-running loops by completing the current workflow instance and
24
+ starting a new one with the provided arguments.
25
+
26
+ The workflow's history is archived (not deleted) and a new instance is created
27
+ with a reference to the previous instance (continued_from).
28
+
29
+ Note:
30
+ This exception should not be caught by user code. It is handled internally
31
+ by the ReplayEngine.
32
+
33
+ Example:
34
+ >>> @workflow
35
+ ... async def notification_service(ctx: WorkflowContext, processed_count: int = 0):
36
+ ... await join_group(ctx, group="order_watchers")
37
+ ...
38
+ ... count = 0
39
+ ... while True:
40
+ ... msg = await wait_message(ctx, channel="order.completed")
41
+ ... await send_notification(ctx, msg.data, activity_id=f"notify:{msg.id}")
42
+ ...
43
+ ... count += 1
44
+ ... if count >= 1000:
45
+ ... # Reset history by recurring with new state
46
+ ... await ctx.recur(processed_count=processed_count + count)
47
+ """
48
+
49
+ def __init__(self, kwargs: dict[str, Any]):
50
+ """
51
+ Initialize RecurException.
52
+
53
+ Args:
54
+ kwargs: Keyword arguments to pass to the new workflow instance
55
+ """
56
+ self.kwargs = kwargs
57
+ super().__init__("Workflow recur requested")
58
+
59
+
17
60
  # Global registry of workflow instances (will be set by EddaApp)
18
61
  _replay_engine: Any = None
19
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Lightweight Durable Execution Framework
5
5
  Project-URL: Homepage, https://github.com/i2y/edda
6
6
  Project-URL: Documentation, https://github.com/i2y/edda#readme
@@ -30,11 +30,13 @@ Requires-Dist: sqlalchemy[asyncio]>=2.0.0
30
30
  Requires-Dist: uvloop>=0.22.1
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: black>=25.9.0; extra == 'dev'
33
+ Requires-Dist: mcp>=1.22.0; extra == 'dev'
33
34
  Requires-Dist: mypy>=1.18.2; extra == 'dev'
34
35
  Requires-Dist: pytest-asyncio>=1.2.0; extra == 'dev'
35
36
  Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
36
37
  Requires-Dist: pytest>=8.4.2; extra == 'dev'
37
38
  Requires-Dist: ruff>=0.14.2; extra == 'dev'
39
+ Requires-Dist: starlette>=0.40.0; extra == 'dev'
38
40
  Requires-Dist: testcontainers[mysql]>=4.0.0; extra == 'dev'
39
41
  Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
40
42
  Requires-Dist: tsuno>=0.1.3; extra == 'dev'
@@ -83,6 +85,7 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
83
85
  - 📦 **Transactional Outbox**: Reliable event publishing with guaranteed delivery
84
86
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
85
87
  - ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
88
+ - 📬 **Channel-based Messaging**: Actor-model style communication with competing (job queue) and broadcast (fan-out) modes
86
89
  - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
87
90
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
88
91
 
@@ -105,14 +108,14 @@ Edda's waiting functions make it ideal for time-based and event-driven business
105
108
  - **📦 Scheduled Notifications**: Shipping updates, subscription renewals, appointment reminders
106
109
 
107
110
  **Waiting functions**:
108
- - `wait_timer(duration_seconds)`: Wait for a relative duration
109
- - `wait_until(until_time)`: Wait until an absolute datetime (e.g., campaign end date)
111
+ - `sleep(seconds)`: Wait for a relative duration
112
+ - `sleep_until(target_time)`: Wait until an absolute datetime (e.g., campaign end date)
110
113
  - `wait_event(event_type)`: Wait for external events (near real-time response)
111
114
 
112
115
  ```python
113
116
  @workflow
114
117
  async def onboarding_reminder(ctx: WorkflowContext, user_id: str):
115
- await wait_timer(ctx, duration_seconds=3*24*60*60) # Wait 3 days
118
+ await sleep(ctx, seconds=3*24*60*60) # Wait 3 days
116
119
  if not await check_completed(ctx, user_id):
117
120
  await send_reminder(ctx, user_id)
118
121
  ```
@@ -164,7 +167,7 @@ graph TB
164
167
 
165
168
  - Multiple workers can run simultaneously across different pods/servers
166
169
  - Each workflow instance runs on only one worker at a time (automatic coordination)
167
- - `wait_event()` and `wait_timer()` free up worker resources while waiting, resume on any worker when event arrives or timer expires
170
+ - `wait_event()` and `sleep()` free up worker resources while waiting, resume on any worker when event arrives or timer expires
168
171
  - Automatic crash recovery with stale lock cleanup and workflow auto-resume
169
172
 
170
173
  ## Quick Start
@@ -484,7 +487,10 @@ Multiple workers can safely process workflows using database-based exclusive con
484
487
 
485
488
  app = EddaApp(
486
489
  db_url="postgresql://localhost/workflows", # Shared database for coordination
487
- service_name="order-service"
490
+ service_name="order-service",
491
+ # Connection pool settings (optional)
492
+ pool_size=5, # Concurrent connections
493
+ max_overflow=10, # Additional burst capacity
488
494
  )
489
495
  ```
490
496
 
@@ -612,10 +618,32 @@ async def payment_workflow(ctx: WorkflowContext, order_id: str):
612
618
  return payment_event.data
613
619
  ```
614
620
 
615
- **wait_timer() for time-based waiting**:
621
+ **ReceivedEvent attributes**: The `wait_event()` function returns a `ReceivedEvent` object:
622
+
623
+ ```python
624
+ event = await wait_event(ctx, "payment.completed")
625
+ amount = event.data["amount"] # Event payload (dict or bytes)
626
+ source = event.metadata.source # CloudEvents source
627
+ event_type = event.metadata.type # CloudEvents type
628
+ extensions = event.extensions # CloudEvents extensions
629
+ ```
630
+
631
+ **Timeout handling with EventTimeoutError**:
616
632
 
617
633
  ```python
618
- from edda import wait_timer
634
+ from edda import wait_event, EventTimeoutError
635
+
636
+ try:
637
+ event = await wait_event(ctx, "payment.completed", timeout_seconds=60)
638
+ except EventTimeoutError:
639
+ # Handle timeout (e.g., cancel order, send reminder)
640
+ await cancel_order(ctx, order_id)
641
+ ```
642
+
643
+ **sleep() for time-based waiting**:
644
+
645
+ ```python
646
+ from edda import sleep
619
647
 
620
648
  @workflow
621
649
  async def order_with_timeout(ctx: WorkflowContext, order_id: str):
@@ -623,7 +651,7 @@ async def order_with_timeout(ctx: WorkflowContext, order_id: str):
623
651
  await create_order(ctx, order_id)
624
652
 
625
653
  # Wait 60 seconds for payment
626
- await wait_timer(ctx, duration_seconds=60)
654
+ await sleep(ctx, seconds=60)
627
655
 
628
656
  # Check payment status
629
657
  return await check_payment(ctx, order_id)
@@ -637,6 +665,136 @@ async def order_with_timeout(ctx: WorkflowContext, order_id: str):
637
665
 
638
666
  **For technical details**, see [Multi-Worker Continuations](local-docs/distributed-coroutines.md).
639
667
 
668
+ ### Message Passing (Workflow-to-Workflow)
669
+
670
+ Edda provides actor-model style message passing for direct workflow-to-workflow communication:
671
+
672
+ ```python
673
+ from edda import workflow, wait_message, send_message_to, WorkflowContext
674
+
675
+ # Receiver workflow - waits for approval message
676
+ @workflow
677
+ async def approval_workflow(ctx: WorkflowContext, request_id: str):
678
+ # Wait for message on "approval" channel
679
+ msg = await wait_message(ctx, channel="approval")
680
+
681
+ if msg.data["approved"]:
682
+ return {"status": "approved", "approver": msg.data["approver"]}
683
+ return {"status": "rejected"}
684
+
685
+ # Sender workflow - sends approval decision
686
+ @workflow
687
+ async def manager_workflow(ctx: WorkflowContext, request_id: str):
688
+ # Review and make decision
689
+ decision = await review_request(ctx, request_id)
690
+
691
+ # Send message to waiting workflow
692
+ await send_message_to(
693
+ ctx,
694
+ target_instance_id=request_id,
695
+ channel="approval",
696
+ data={"approved": decision, "approver": "manager-123"},
697
+ )
698
+ ```
699
+
700
+ **Group Communication (Erlang pg style)** - for fan-out messaging without knowing receiver instance IDs:
701
+
702
+ ```python
703
+ from edda import workflow, join_group, wait_message, publish_to_group
704
+
705
+ # Receiver workflow - joins a group and listens
706
+ @workflow
707
+ async def notification_service(ctx: WorkflowContext, service_id: str):
708
+ # Join group at startup (loose coupling - sender doesn't need to know us)
709
+ await join_group(ctx, group="order_watchers")
710
+
711
+ while True:
712
+ msg = await wait_message(ctx, channel="order.created")
713
+ await send_notification(ctx, msg.data)
714
+
715
+ # Sender workflow - publishes to all group members
716
+ @workflow
717
+ async def order_processor(ctx: WorkflowContext, order_id: str):
718
+ result = await process_order(ctx, order_id)
719
+
720
+ # Broadcast to all watchers (doesn't need to know instance IDs)
721
+ count = await publish_to_group(
722
+ ctx,
723
+ group="order_watchers",
724
+ channel="order.created",
725
+ data={"order_id": order_id, "status": "completed"},
726
+ )
727
+ print(f"Notified {count} watchers")
728
+ ```
729
+
730
+ **Channel API with Delivery Modes** - subscribe to channels with explicit delivery semantics:
731
+
732
+ ```python
733
+ from edda import workflow, subscribe, receive, publish, WorkflowContext
734
+
735
+ # Job Worker - processes jobs exclusively (competing mode)
736
+ @workflow
737
+ async def job_worker(ctx: WorkflowContext, worker_id: str):
738
+ # Subscribe with competing mode - each job goes to ONE worker only
739
+ await subscribe(ctx, channel="jobs", mode="competing")
740
+
741
+ while True:
742
+ job = await receive(ctx, channel="jobs") # Get next job
743
+ await process_job(ctx, job.data)
744
+ await ctx.recur(worker_id) # Continue processing
745
+
746
+ # Notification Handler - receives ALL messages (broadcast mode)
747
+ @workflow
748
+ async def notification_handler(ctx: WorkflowContext, handler_id: str):
749
+ # Subscribe with broadcast mode - ALL handlers receive each message
750
+ await subscribe(ctx, channel="notifications", mode="broadcast")
751
+
752
+ while True:
753
+ msg = await receive(ctx, channel="notifications")
754
+ await send_notification(ctx, msg.data)
755
+ await ctx.recur(handler_id)
756
+
757
+ # Publisher - send messages to channel
758
+ await publish(ctx, channel="jobs", data={"task": "send_report"})
759
+ ```
760
+
761
+ **Delivery modes**:
762
+ - **`competing`**: Each message goes to exactly ONE subscriber (job queue/task distribution)
763
+ - **`broadcast`**: Each message goes to ALL subscribers (notifications/fan-out)
764
+
765
+ **Key features**:
766
+ - **Channel-based messaging**: Messages are delivered to workflows waiting on specific channels
767
+ - **Competing vs Broadcast**: Choose semantics per subscription
768
+ - **Group communication**: Erlang pg-style groups for loose coupling and fan-out
769
+ - **Database-backed**: All messages are persisted for durability
770
+ - **Lock-first delivery**: Safe for multi-worker environments
771
+
772
+ ### Workflow Recurrence
773
+
774
+ Long-running workflows can use `ctx.recur()` to restart with fresh history while maintaining the same instance ID. This is essential for workflows that run indefinitely (job workers, notification handlers, etc.):
775
+
776
+ ```python
777
+ from edda import workflow, subscribe, receive, WorkflowContext
778
+
779
+ @workflow
780
+ async def job_worker(ctx: WorkflowContext, worker_id: str):
781
+ await subscribe(ctx, channel="jobs", mode="competing")
782
+
783
+ # Process one job
784
+ job = await receive(ctx, channel="jobs")
785
+ await process_job(ctx, job.data)
786
+
787
+ # Archive history and restart with same instance_id
788
+ # Prevents unbounded history growth
789
+ await ctx.recur(worker_id)
790
+ ```
791
+
792
+ **Key benefits**:
793
+ - **Prevents history growth**: Archives old history, starts fresh
794
+ - **Maintains instance ID**: Same workflow continues logically
795
+ - **Preserves subscriptions**: Channel subscriptions survive recurrence
796
+ - **Enables infinite loops**: Essential for long-running workers
797
+
640
798
  ### ASGI Integration
641
799
 
642
800
  Edda runs as an ASGI application:
@@ -1,23 +1,23 @@
1
- edda/__init__.py,sha256=gmJd0ooVbGNMOLlSj-r6rEt3IkY-FZYCFjhWjIltqlk,1657
1
+ edda/__init__.py,sha256=hGC6WR2R36M8LWC97F-0Rw4Ln0QUUT_1xC-7acOy_Fk,2237
2
2
  edda/activity.py,sha256=nRm9eBrr0lFe4ZRQ2whyZ6mo5xd171ITIVhqytUhOpw,21025
3
- edda/app.py,sha256=8FKx9Kspbm5Fz-QDz4DgncNkkgdZkij209LHllWkRw4,38288
4
- edda/compensation.py,sha256=CmnyJy4jAklVrtLJodNOcj6vxET6pdarxM1Yx2RHlL4,11898
5
- edda/context.py,sha256=YZKBNtblRcaFqte1Y9t2cIP3JHzK-5Tu40x5i5FHtnU,17789
6
- edda/events.py,sha256=KN06o-Umkwkg9-TwbN4jr1uBZrBrvVSc6m8mOlQGXkA,18043
3
+ edda/app.py,sha256=kZ-VEvjIe3GjUA8RhT6OimuezNyPf2IhrvQ2kL44zJs,45201
4
+ edda/channels.py,sha256=ozaXCcWMLwgu_i6p8I79C9FnfsoQ0uv2BpaPPonTJdc,33863
5
+ edda/compensation.py,sha256=iKLlnTxiF1YSatmYQW84EkPB1yMKUEZBtgjuGnghLtY,11824
6
+ edda/context.py,sha256=kxWok86IwLF7hvxlE803t7ayPySTzCKoW113TmwON1k,19752
7
7
  edda/exceptions.py,sha256=-ntBLGpVQgPFG5N1o8m_7weejAYkNrUdxTkOP38vsHk,1766
8
8
  edda/hooks.py,sha256=HUZ6FTM__DZjwuomDfTDEroQ3mugEPuJHcGm7CTQNvg,8193
9
- edda/locking.py,sha256=l3YM7zdERizw27jQXfLN7EmcMcrJSVzd7LD8hhsXvIM,11003
9
+ edda/locking.py,sha256=NAFJmw-JaSVsXn4Y4czJyv_s9bWG8cdrzDBWIEag5X8,13661
10
10
  edda/pydantic_utils.py,sha256=dGVPNrrttDeq1k233PopCtjORYjZitsgASPfPnO6R10,9056
11
- edda/replay.py,sha256=5RIRd0q2ZrH9iiiy35eOUii2cipYg9dlua56OAXvIk4,32499
11
+ edda/replay.py,sha256=_poGUfvsDJP8GiflAw6aCZzxMKJpo99z__JVdGHb75I,42567
12
12
  edda/retry.py,sha256=t4_E1skrhotA1XWHTLbKi-DOgCMasOUnhI9OT-O_eCE,6843
13
- edda/workflow.py,sha256=daSppYAzgXkjY_9-HS93Zi7_tPR6srmchxY5YfwgU-4,7239
13
+ edda/workflow.py,sha256=hfBZM0JrtK0IkvZSrva0VmYVyvKCdiJ5FWFmIVENfrM,8807
14
14
  edda/wsgi.py,sha256=1pGE5fhHpcsYnDR8S3NEFKWUs5P0JK4roTAzX9BsIj0,2391
15
15
  edda/integrations/__init__.py,sha256=F_CaTvlDEbldfOpPKq_U9ve1E573tS6XzqXnOtyHcXI,33
16
16
  edda/integrations/mcp/__init__.py,sha256=YK-8m0DIdP-RSqewlIX7xnWU7TD3NioCiW2_aZSgnn8,1232
17
17
  edda/integrations/mcp/decorators.py,sha256=31SmbDwmHEGvUNa3aaatW91hBkpnS5iN9uy47dID3J4,10037
18
18
  edda/integrations/mcp/server.py,sha256=Q5r4AbMn-9gBcy2CZocbgW7O0fn7Qb4e9CBJa1FEmzU,14507
19
19
  edda/integrations/opentelemetry/__init__.py,sha256=x1_PyyygGDW-rxQTwoIrGzyjKErXHOOKdquFAMlCOAo,906
20
- edda/integrations/opentelemetry/hooks.py,sha256=FBYnSZBh8_0vw9M1E2AbJrx1cTTsKeiHf5wspr0UnzU,21288
20
+ edda/integrations/opentelemetry/hooks.py,sha256=rCb6K_gJJMxjQ-UoJnbIOWsafapipzu7w-YPROZKxDA,21330
21
21
  edda/outbox/__init__.py,sha256=azXG1rtheJEjOyoWmMsBeR2jp8Bz02R3wDEd5tQnaWA,424
22
22
  edda/outbox/relayer.py,sha256=2tnN1aOQ8pKWfwEGIlYwYLLwyOKXBjZ4XZsIr1HjgK4,9454
23
23
  edda/outbox/transactional.py,sha256=LFfUjunqRlGibaINi-efGXFFivWGO7v3mhqrqyGW6Nw,3808
@@ -25,18 +25,19 @@ edda/serialization/__init__.py,sha256=hnOVJN-mJNIsSa_XH9jwhIydOsWvIfCaFaSd37HUpl
25
25
  edda/serialization/base.py,sha256=xJy2CY9gdJDCF0tmCor8NomL2Lr_w7cveVvxccuc-tA,1998
26
26
  edda/serialization/json.py,sha256=Dq96V4n1yozexjCPd_CL6Iuvh1u3jJhef6sTcNxXZeA,2842
27
27
  edda/storage/__init__.py,sha256=Q-kNJsjF8hMc2Q5MYFlLBENKExlNlKkbmUkwBOosj9I,216
28
- edda/storage/models.py,sha256=usVPKLV4H1-LA6QC72_gkfYrG9T8AwkcmCXppBo-2gQ,7338
29
- edda/storage/protocol.py,sha256=ws9gDhiHpL1XglkdbN_-ayP_2GiIc-B9qt8bcrO2TG0,24957
30
- edda/storage/sqlalchemy_storage.py,sha256=z3OepqxVUHnknnPJj19Vii3SB4n_RI_Zu7Tz70gtwOI,71144
28
+ edda/storage/models.py,sha256=E_KFygX6DP2qRwgLGLiKPhcXdKtvnfKeqA2kMXcAchE,13188
29
+ edda/storage/protocol.py,sha256=0E5QSUDtSMlJX_lF6m3VAUx3WO6Qjm-Kq6_bXI7P52I,39841
30
+ edda/storage/sqlalchemy_storage.py,sha256=eswQxCyY2DdUNYdrkxW_7Pev6CHCohNb72kGJjf29tA,140825
31
31
  edda/viewer_ui/__init__.py,sha256=N1-T33SXadOXcBsDSgJJ9Iqz4y4verJngWryQu70c5c,517
32
- edda/viewer_ui/app.py,sha256=VXVEiS07w76GGpTybWcgUeygfCUFpmAbGK_CxMk6EFs,75455
33
- edda/viewer_ui/components.py,sha256=Fx78Im3-TvAfwIckGKmvdT_qMKw5m_qrwNcQEI7ci6M,44664
34
- edda/viewer_ui/data_service.py,sha256=mXV6bL6REa_UKsk8xMGBIFbsbLpIxe91lX3wgn-FOjw,34843
32
+ edda/viewer_ui/app.py,sha256=CqHKsUj5pcysHCk0aRfkEqV4DIV4l3GzOPKBJ5DTYOQ,95624
33
+ edda/viewer_ui/components.py,sha256=A0IxLwgj_Lu51O57OfzOwME8jzoJtKegEVvSnWc7uPo,45174
34
+ edda/viewer_ui/data_service.py,sha256=yzmPz67rBoECY7eNK5nl6YS3jIZAi-haaqrP0GIgJYE,36373
35
+ edda/viewer_ui/theme.py,sha256=mrXoXLRzgSnvE2a58LuMcPJkhlvHEDMWVa8Smqtk4l0,8118
35
36
  edda/visualizer/__init__.py,sha256=DOpDstNhR0VcXAs_eMKxaL30p_0u4PKZ4o2ndnYhiRo,343
36
37
  edda/visualizer/ast_analyzer.py,sha256=plmx7C9X_X35xLY80jxOL3ljg3afXxBePRZubqUIkxY,13663
37
38
  edda/visualizer/mermaid_generator.py,sha256=XWa2egoOTNDfJEjPcwoxwQmblUqXf7YInWFjFRI1QGo,12457
38
- edda_framework-0.6.0.dist-info/METADATA,sha256=sxR8GtZNOzswVHIh8n_uAojtOrUEn_mXbT9Li5IvTnk,31702
39
- edda_framework-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
- edda_framework-0.6.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
41
- edda_framework-0.6.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
42
- edda_framework-0.6.0.dist-info/RECORD,,
39
+ edda_framework-0.8.0.dist-info/METADATA,sha256=o5ANdknKkLtOAGnKZ_IIQ0D3Q2iKyccdYVB-jjf8LLA,37506
40
+ edda_framework-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
41
+ edda_framework-0.8.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
42
+ edda_framework-0.8.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
43
+ edda_framework-0.8.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any