edda-framework 0.7.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.
- edda/__init__.py +39 -5
- edda/app.py +383 -223
- edda/channels.py +992 -0
- edda/compensation.py +22 -22
- edda/context.py +77 -51
- edda/integrations/opentelemetry/hooks.py +7 -2
- edda/locking.py +130 -67
- edda/replay.py +312 -82
- edda/storage/models.py +165 -24
- edda/storage/protocol.py +557 -118
- edda/storage/sqlalchemy_storage.py +1968 -314
- edda/viewer_ui/app.py +6 -1
- edda/viewer_ui/data_service.py +19 -22
- edda/workflow.py +43 -0
- {edda_framework-0.7.0.dist-info → edda_framework-0.8.0.dist-info}/METADATA +165 -9
- {edda_framework-0.7.0.dist-info → edda_framework-0.8.0.dist-info}/RECORD +19 -19
- edda/events.py +0 -505
- {edda_framework-0.7.0.dist-info → edda_framework-0.8.0.dist-info}/WHEEL +0 -0
- {edda_framework-0.7.0.dist-info → edda_framework-0.8.0.dist-info}/entry_points.txt +0 -0
- {edda_framework-0.7.0.dist-info → edda_framework-0.8.0.dist-info}/licenses/LICENSE +0 -0
edda/viewer_ui/app.py
CHANGED
|
@@ -1600,7 +1600,12 @@ def start_viewer(edda_app: EddaApp, port: int = 8080, reload: bool = False) -> N
|
|
|
1600
1600
|
with ui.row().classes("gap-4 items-center"):
|
|
1601
1601
|
# Cancel button (only show for running/waiting workflows)
|
|
1602
1602
|
status = instance["status"]
|
|
1603
|
-
if status in [
|
|
1603
|
+
if status in [
|
|
1604
|
+
"running",
|
|
1605
|
+
"waiting_for_event",
|
|
1606
|
+
"waiting_for_timer",
|
|
1607
|
+
"waiting_for_message",
|
|
1608
|
+
]:
|
|
1604
1609
|
|
|
1605
1610
|
async def handle_cancel() -> None:
|
|
1606
1611
|
"""Handle workflow cancellation."""
|
edda/viewer_ui/data_service.py
CHANGED
|
@@ -267,7 +267,7 @@ class WorkflowDataService:
|
|
|
267
267
|
except (OSError, TypeError) as e:
|
|
268
268
|
# OSError: source not available (e.g., interactive shell)
|
|
269
269
|
# TypeError: not a module, class, method, function, etc.
|
|
270
|
-
|
|
270
|
+
logger.warning("Could not get source for %s: %s", workflow_name, e)
|
|
271
271
|
return None
|
|
272
272
|
|
|
273
273
|
async def get_activity_executions(
|
|
@@ -332,8 +332,8 @@ class WorkflowDataService:
|
|
|
332
332
|
import httpx
|
|
333
333
|
|
|
334
334
|
try:
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
logger.debug("Attempting to cancel workflow: %s", instance_id)
|
|
336
|
+
logger.debug("API URL: %s/cancel/%s", edda_app_url, instance_id)
|
|
337
337
|
|
|
338
338
|
async with httpx.AsyncClient() as client:
|
|
339
339
|
response = await client.post(
|
|
@@ -341,8 +341,8 @@ class WorkflowDataService:
|
|
|
341
341
|
timeout=10.0,
|
|
342
342
|
)
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
logger.debug("Response status: %s", response.status_code)
|
|
345
|
+
logger.debug("Response body: %s", response.text)
|
|
346
346
|
|
|
347
347
|
if 200 <= response.status_code < 300:
|
|
348
348
|
return True, "Workflow cancelled successfully"
|
|
@@ -356,17 +356,17 @@ class WorkflowDataService:
|
|
|
356
356
|
|
|
357
357
|
except httpx.ConnectError as e:
|
|
358
358
|
error_msg = f"Cannot connect to EddaApp at {edda_app_url}. Is it running?"
|
|
359
|
-
|
|
359
|
+
logger.warning("Connection error: %s", e)
|
|
360
360
|
return False, error_msg
|
|
361
361
|
|
|
362
362
|
except httpx.TimeoutException as e:
|
|
363
363
|
error_msg = "Request timed out. The server may be busy."
|
|
364
|
-
|
|
364
|
+
logger.warning("Timeout error: %s", e)
|
|
365
365
|
return False, error_msg
|
|
366
366
|
|
|
367
367
|
except Exception as e:
|
|
368
368
|
error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}"
|
|
369
|
-
|
|
369
|
+
logger.error("Unexpected error: %s", e, exc_info=True)
|
|
370
370
|
return False, error_msg
|
|
371
371
|
|
|
372
372
|
def get_all_workflows(self) -> dict[str, Any]:
|
|
@@ -520,7 +520,7 @@ class WorkflowDataService:
|
|
|
520
520
|
}
|
|
521
521
|
except Exception as e:
|
|
522
522
|
# Fallback to JSON if schema generation fails
|
|
523
|
-
|
|
523
|
+
logger.warning("Failed to generate JSON Schema for %s: %s", annotation, e)
|
|
524
524
|
return {"type": "json"}
|
|
525
525
|
|
|
526
526
|
# Check if it's an Enum
|
|
@@ -856,9 +856,9 @@ class WorkflowDataService:
|
|
|
856
856
|
from cloudevents.http import CloudEvent
|
|
857
857
|
|
|
858
858
|
try:
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
859
|
+
logger.debug("Attempting to start workflow: %s", workflow_name)
|
|
860
|
+
logger.debug("Sending CloudEvent to: %s", edda_app_url)
|
|
861
|
+
logger.debug("Params: %s", params)
|
|
862
862
|
|
|
863
863
|
# Verify workflow exists
|
|
864
864
|
all_workflows = self.get_all_workflows()
|
|
@@ -876,8 +876,8 @@ class WorkflowDataService:
|
|
|
876
876
|
# Convert to HTTP format (structured content mode)
|
|
877
877
|
headers, body = to_structured(event)
|
|
878
878
|
|
|
879
|
-
|
|
880
|
-
|
|
879
|
+
logger.debug("CloudEvent ID: %s", attributes["id"])
|
|
880
|
+
logger.debug("CloudEvent type: %s", workflow_name)
|
|
881
881
|
|
|
882
882
|
# Send CloudEvent to EddaApp
|
|
883
883
|
async with httpx.AsyncClient() as client:
|
|
@@ -888,8 +888,8 @@ class WorkflowDataService:
|
|
|
888
888
|
timeout=10.0,
|
|
889
889
|
)
|
|
890
890
|
|
|
891
|
-
|
|
892
|
-
|
|
891
|
+
logger.debug("Response status: %s", response.status_code)
|
|
892
|
+
logger.debug("Response body: %s", response.text)
|
|
893
893
|
|
|
894
894
|
if 200 <= response.status_code < 300:
|
|
895
895
|
# CloudEvent accepted (200 OK or 202 Accepted)
|
|
@@ -902,18 +902,15 @@ class WorkflowDataService:
|
|
|
902
902
|
|
|
903
903
|
except httpx.ConnectError as e:
|
|
904
904
|
error_msg = f"Cannot connect to EddaApp at {edda_app_url}. Is it running?"
|
|
905
|
-
|
|
905
|
+
logger.warning("Connection error: %s", e)
|
|
906
906
|
return False, error_msg, None
|
|
907
907
|
|
|
908
908
|
except httpx.TimeoutException as e:
|
|
909
909
|
error_msg = "Request timed out. The server may be busy."
|
|
910
|
-
|
|
910
|
+
logger.warning("Timeout error: %s", e)
|
|
911
911
|
return False, error_msg, None
|
|
912
912
|
|
|
913
913
|
except Exception as e:
|
|
914
914
|
error_msg = f"Unexpected error: {type(e).__name__}: {str(e)}"
|
|
915
|
-
|
|
916
|
-
import traceback
|
|
917
|
-
|
|
918
|
-
traceback.print_exc()
|
|
915
|
+
logger.error("Unexpected error: %s", e, exc_info=True)
|
|
919
916
|
return False, error_msg, None
|
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.
|
|
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
|
|
@@ -85,6 +85,7 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
|
|
|
85
85
|
- 📦 **Transactional Outbox**: Reliable event publishing with guaranteed delivery
|
|
86
86
|
- ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
|
|
87
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
|
|
88
89
|
- 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
|
|
89
90
|
- 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
|
|
90
91
|
|
|
@@ -107,14 +108,14 @@ Edda's waiting functions make it ideal for time-based and event-driven business
|
|
|
107
108
|
- **📦 Scheduled Notifications**: Shipping updates, subscription renewals, appointment reminders
|
|
108
109
|
|
|
109
110
|
**Waiting functions**:
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
111
|
+
- `sleep(seconds)`: Wait for a relative duration
|
|
112
|
+
- `sleep_until(target_time)`: Wait until an absolute datetime (e.g., campaign end date)
|
|
112
113
|
- `wait_event(event_type)`: Wait for external events (near real-time response)
|
|
113
114
|
|
|
114
115
|
```python
|
|
115
116
|
@workflow
|
|
116
117
|
async def onboarding_reminder(ctx: WorkflowContext, user_id: str):
|
|
117
|
-
await
|
|
118
|
+
await sleep(ctx, seconds=3*24*60*60) # Wait 3 days
|
|
118
119
|
if not await check_completed(ctx, user_id):
|
|
119
120
|
await send_reminder(ctx, user_id)
|
|
120
121
|
```
|
|
@@ -166,7 +167,7 @@ graph TB
|
|
|
166
167
|
|
|
167
168
|
- Multiple workers can run simultaneously across different pods/servers
|
|
168
169
|
- Each workflow instance runs on only one worker at a time (automatic coordination)
|
|
169
|
-
- `wait_event()` and `
|
|
170
|
+
- `wait_event()` and `sleep()` free up worker resources while waiting, resume on any worker when event arrives or timer expires
|
|
170
171
|
- Automatic crash recovery with stale lock cleanup and workflow auto-resume
|
|
171
172
|
|
|
172
173
|
## Quick Start
|
|
@@ -486,7 +487,10 @@ Multiple workers can safely process workflows using database-based exclusive con
|
|
|
486
487
|
|
|
487
488
|
app = EddaApp(
|
|
488
489
|
db_url="postgresql://localhost/workflows", # Shared database for coordination
|
|
489
|
-
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
|
|
490
494
|
)
|
|
491
495
|
```
|
|
492
496
|
|
|
@@ -614,10 +618,32 @@ async def payment_workflow(ctx: WorkflowContext, order_id: str):
|
|
|
614
618
|
return payment_event.data
|
|
615
619
|
```
|
|
616
620
|
|
|
617
|
-
**
|
|
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**:
|
|
618
632
|
|
|
619
633
|
```python
|
|
620
|
-
from edda import
|
|
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
|
|
621
647
|
|
|
622
648
|
@workflow
|
|
623
649
|
async def order_with_timeout(ctx: WorkflowContext, order_id: str):
|
|
@@ -625,7 +651,7 @@ async def order_with_timeout(ctx: WorkflowContext, order_id: str):
|
|
|
625
651
|
await create_order(ctx, order_id)
|
|
626
652
|
|
|
627
653
|
# Wait 60 seconds for payment
|
|
628
|
-
await
|
|
654
|
+
await sleep(ctx, seconds=60)
|
|
629
655
|
|
|
630
656
|
# Check payment status
|
|
631
657
|
return await check_payment(ctx, order_id)
|
|
@@ -639,6 +665,136 @@ async def order_with_timeout(ctx: WorkflowContext, order_id: str):
|
|
|
639
665
|
|
|
640
666
|
**For technical details**, see [Multi-Worker Continuations](local-docs/distributed-coroutines.md).
|
|
641
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
|
+
|
|
642
798
|
### ASGI Integration
|
|
643
799
|
|
|
644
800
|
Edda runs as an ASGI application:
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
edda/__init__.py,sha256=
|
|
1
|
+
edda/__init__.py,sha256=hGC6WR2R36M8LWC97F-0Rw4Ln0QUUT_1xC-7acOy_Fk,2237
|
|
2
2
|
edda/activity.py,sha256=nRm9eBrr0lFe4ZRQ2whyZ6mo5xd171ITIVhqytUhOpw,21025
|
|
3
|
-
edda/app.py,sha256=
|
|
4
|
-
edda/
|
|
5
|
-
edda/
|
|
6
|
-
edda/
|
|
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=
|
|
9
|
+
edda/locking.py,sha256=NAFJmw-JaSVsXn4Y4czJyv_s9bWG8cdrzDBWIEag5X8,13661
|
|
10
10
|
edda/pydantic_utils.py,sha256=dGVPNrrttDeq1k233PopCtjORYjZitsgASPfPnO6R10,9056
|
|
11
|
-
edda/replay.py,sha256=
|
|
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=
|
|
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=
|
|
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,19 +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=
|
|
29
|
-
edda/storage/protocol.py,sha256=
|
|
30
|
-
edda/storage/sqlalchemy_storage.py,sha256=
|
|
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=
|
|
32
|
+
edda/viewer_ui/app.py,sha256=CqHKsUj5pcysHCk0aRfkEqV4DIV4l3GzOPKBJ5DTYOQ,95624
|
|
33
33
|
edda/viewer_ui/components.py,sha256=A0IxLwgj_Lu51O57OfzOwME8jzoJtKegEVvSnWc7uPo,45174
|
|
34
|
-
edda/viewer_ui/data_service.py,sha256=
|
|
34
|
+
edda/viewer_ui/data_service.py,sha256=yzmPz67rBoECY7eNK5nl6YS3jIZAi-haaqrP0GIgJYE,36373
|
|
35
35
|
edda/viewer_ui/theme.py,sha256=mrXoXLRzgSnvE2a58LuMcPJkhlvHEDMWVa8Smqtk4l0,8118
|
|
36
36
|
edda/visualizer/__init__.py,sha256=DOpDstNhR0VcXAs_eMKxaL30p_0u4PKZ4o2ndnYhiRo,343
|
|
37
37
|
edda/visualizer/ast_analyzer.py,sha256=plmx7C9X_X35xLY80jxOL3ljg3afXxBePRZubqUIkxY,13663
|
|
38
38
|
edda/visualizer/mermaid_generator.py,sha256=XWa2egoOTNDfJEjPcwoxwQmblUqXf7YInWFjFRI1QGo,12457
|
|
39
|
-
edda_framework-0.
|
|
40
|
-
edda_framework-0.
|
|
41
|
-
edda_framework-0.
|
|
42
|
-
edda_framework-0.
|
|
43
|
-
edda_framework-0.
|
|
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,,
|