edda-framework 0.7.0__py3-none-any.whl → 0.9.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 +1017 -0
- edda/compensation.py +22 -22
- edda/context.py +105 -52
- edda/integrations/opentelemetry/hooks.py +7 -2
- edda/locking.py +130 -67
- edda/replay.py +312 -82
- edda/storage/models.py +142 -24
- edda/storage/protocol.py +539 -118
- edda/storage/sqlalchemy_storage.py +1866 -328
- 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.9.0.dist-info}/METADATA +109 -9
- {edda_framework-0.7.0.dist-info → edda_framework-0.9.0.dist-info}/RECORD +19 -19
- edda/events.py +0 -505
- {edda_framework-0.7.0.dist-info → edda_framework-0.9.0.dist-info}/WHEEL +0 -0
- {edda_framework-0.7.0.dist-info → edda_framework-0.9.0.dist-info}/entry_points.txt +0 -0
- {edda_framework-0.7.0.dist-info → edda_framework-0.9.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.9.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
|
|
@@ -65,6 +65,7 @@ Description-Content-Type: text/markdown
|
|
|
65
65
|
[](https://opensource.org/licenses/MIT)
|
|
66
66
|
[](https://www.python.org/downloads/)
|
|
67
67
|
[](https://i2y.github.io/edda/)
|
|
68
|
+
[](https://deepwiki.com/i2y/edda)
|
|
68
69
|
|
|
69
70
|
## Overview
|
|
70
71
|
|
|
@@ -85,6 +86,7 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
|
|
|
85
86
|
- 📦 **Transactional Outbox**: Reliable event publishing with guaranteed delivery
|
|
86
87
|
- ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
|
|
87
88
|
- ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
|
|
89
|
+
- 📬 **Channel-based Messaging**: Actor-model style communication with competing (job queue) and broadcast (fan-out) modes
|
|
88
90
|
- 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
|
|
89
91
|
- 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
|
|
90
92
|
|
|
@@ -107,14 +109,14 @@ Edda's waiting functions make it ideal for time-based and event-driven business
|
|
|
107
109
|
- **📦 Scheduled Notifications**: Shipping updates, subscription renewals, appointment reminders
|
|
108
110
|
|
|
109
111
|
**Waiting functions**:
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
112
|
+
- `sleep(seconds)`: Wait for a relative duration
|
|
113
|
+
- `sleep_until(target_time)`: Wait until an absolute datetime (e.g., campaign end date)
|
|
112
114
|
- `wait_event(event_type)`: Wait for external events (near real-time response)
|
|
113
115
|
|
|
114
116
|
```python
|
|
115
117
|
@workflow
|
|
116
118
|
async def onboarding_reminder(ctx: WorkflowContext, user_id: str):
|
|
117
|
-
await
|
|
119
|
+
await sleep(ctx, seconds=3*24*60*60) # Wait 3 days
|
|
118
120
|
if not await check_completed(ctx, user_id):
|
|
119
121
|
await send_reminder(ctx, user_id)
|
|
120
122
|
```
|
|
@@ -166,7 +168,7 @@ graph TB
|
|
|
166
168
|
|
|
167
169
|
- Multiple workers can run simultaneously across different pods/servers
|
|
168
170
|
- Each workflow instance runs on only one worker at a time (automatic coordination)
|
|
169
|
-
- `wait_event()` and `
|
|
171
|
+
- `wait_event()` and `sleep()` free up worker resources while waiting, resume on any worker when event arrives or timer expires
|
|
170
172
|
- Automatic crash recovery with stale lock cleanup and workflow auto-resume
|
|
171
173
|
|
|
172
174
|
## Quick Start
|
|
@@ -486,7 +488,10 @@ Multiple workers can safely process workflows using database-based exclusive con
|
|
|
486
488
|
|
|
487
489
|
app = EddaApp(
|
|
488
490
|
db_url="postgresql://localhost/workflows", # Shared database for coordination
|
|
489
|
-
service_name="order-service"
|
|
491
|
+
service_name="order-service",
|
|
492
|
+
# Connection pool settings (optional)
|
|
493
|
+
pool_size=5, # Concurrent connections
|
|
494
|
+
max_overflow=10, # Additional burst capacity
|
|
490
495
|
)
|
|
491
496
|
```
|
|
492
497
|
|
|
@@ -614,10 +619,32 @@ async def payment_workflow(ctx: WorkflowContext, order_id: str):
|
|
|
614
619
|
return payment_event.data
|
|
615
620
|
```
|
|
616
621
|
|
|
617
|
-
**
|
|
622
|
+
**ReceivedEvent attributes**: The `wait_event()` function returns a `ReceivedEvent` object:
|
|
623
|
+
|
|
624
|
+
```python
|
|
625
|
+
event = await wait_event(ctx, "payment.completed")
|
|
626
|
+
amount = event.data["amount"] # Event payload (dict or bytes)
|
|
627
|
+
source = event.metadata.source # CloudEvents source
|
|
628
|
+
event_type = event.metadata.type # CloudEvents type
|
|
629
|
+
extensions = event.extensions # CloudEvents extensions
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Timeout handling with EventTimeoutError**:
|
|
633
|
+
|
|
634
|
+
```python
|
|
635
|
+
from edda import wait_event, EventTimeoutError
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
event = await wait_event(ctx, "payment.completed", timeout_seconds=60)
|
|
639
|
+
except EventTimeoutError:
|
|
640
|
+
# Handle timeout (e.g., cancel order, send reminder)
|
|
641
|
+
await cancel_order(ctx, order_id)
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**sleep() for time-based waiting**:
|
|
618
645
|
|
|
619
646
|
```python
|
|
620
|
-
from edda import
|
|
647
|
+
from edda import sleep
|
|
621
648
|
|
|
622
649
|
@workflow
|
|
623
650
|
async def order_with_timeout(ctx: WorkflowContext, order_id: str):
|
|
@@ -625,7 +652,7 @@ async def order_with_timeout(ctx: WorkflowContext, order_id: str):
|
|
|
625
652
|
await create_order(ctx, order_id)
|
|
626
653
|
|
|
627
654
|
# Wait 60 seconds for payment
|
|
628
|
-
await
|
|
655
|
+
await sleep(ctx, seconds=60)
|
|
629
656
|
|
|
630
657
|
# Check payment status
|
|
631
658
|
return await check_payment(ctx, order_id)
|
|
@@ -639,6 +666,79 @@ async def order_with_timeout(ctx: WorkflowContext, order_id: str):
|
|
|
639
666
|
|
|
640
667
|
**For technical details**, see [Multi-Worker Continuations](local-docs/distributed-coroutines.md).
|
|
641
668
|
|
|
669
|
+
### Channel-based Messaging
|
|
670
|
+
|
|
671
|
+
Edda provides channel-based messaging for workflow-to-workflow communication with two delivery modes:
|
|
672
|
+
|
|
673
|
+
```python
|
|
674
|
+
from edda import workflow, subscribe, receive, publish, send_to, WorkflowContext
|
|
675
|
+
|
|
676
|
+
# Job Worker - processes jobs exclusively (competing mode)
|
|
677
|
+
@workflow
|
|
678
|
+
async def job_worker(ctx: WorkflowContext, worker_id: str):
|
|
679
|
+
# Subscribe with competing mode - each job goes to ONE worker only
|
|
680
|
+
await subscribe(ctx, channel="jobs", mode="competing")
|
|
681
|
+
|
|
682
|
+
while True:
|
|
683
|
+
job = await receive(ctx, channel="jobs") # Get next job
|
|
684
|
+
await process_job(ctx, job.data)
|
|
685
|
+
await ctx.recur(worker_id) # Continue processing
|
|
686
|
+
|
|
687
|
+
# Notification Handler - receives ALL messages (broadcast mode)
|
|
688
|
+
@workflow
|
|
689
|
+
async def notification_handler(ctx: WorkflowContext, handler_id: str):
|
|
690
|
+
# Subscribe with broadcast mode - ALL handlers receive each message
|
|
691
|
+
await subscribe(ctx, channel="notifications", mode="broadcast")
|
|
692
|
+
|
|
693
|
+
while True:
|
|
694
|
+
msg = await receive(ctx, channel="notifications")
|
|
695
|
+
await send_notification(ctx, msg.data)
|
|
696
|
+
await ctx.recur(handler_id)
|
|
697
|
+
|
|
698
|
+
# Publish to channel (all subscribers or one competing subscriber)
|
|
699
|
+
await publish(ctx, channel="jobs", data={"task": "send_report"})
|
|
700
|
+
|
|
701
|
+
# Direct message to specific workflow instance
|
|
702
|
+
await send_to(ctx, instance_id="workflow-123", channel="approval", data={"approved": True})
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
**Delivery modes**:
|
|
706
|
+
- **`competing`**: Each message goes to exactly ONE subscriber (job queue/task distribution)
|
|
707
|
+
- **`broadcast`**: Each message goes to ALL subscribers (notifications/fan-out)
|
|
708
|
+
|
|
709
|
+
**Key features**:
|
|
710
|
+
- **Channel-based messaging**: Messages are delivered to workflows waiting on specific channels
|
|
711
|
+
- **Competing vs Broadcast**: Choose semantics per subscription
|
|
712
|
+
- **Direct messaging**: `send_to()` for workflow-to-workflow communication
|
|
713
|
+
- **Database-backed**: All messages are persisted for durability
|
|
714
|
+
- **Lock-first delivery**: Safe for multi-worker environments
|
|
715
|
+
|
|
716
|
+
### Workflow Recurrence
|
|
717
|
+
|
|
718
|
+
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.):
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
from edda import workflow, subscribe, receive, WorkflowContext
|
|
722
|
+
|
|
723
|
+
@workflow
|
|
724
|
+
async def job_worker(ctx: WorkflowContext, worker_id: str):
|
|
725
|
+
await subscribe(ctx, channel="jobs", mode="competing")
|
|
726
|
+
|
|
727
|
+
# Process one job
|
|
728
|
+
job = await receive(ctx, channel="jobs")
|
|
729
|
+
await process_job(ctx, job.data)
|
|
730
|
+
|
|
731
|
+
# Archive history and restart with same instance_id
|
|
732
|
+
# Prevents unbounded history growth
|
|
733
|
+
await ctx.recur(worker_id)
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
**Key benefits**:
|
|
737
|
+
- **Prevents history growth**: Archives old history, starts fresh
|
|
738
|
+
- **Maintains instance ID**: Same workflow continues logically
|
|
739
|
+
- **Preserves subscriptions**: Channel subscriptions survive recurrence
|
|
740
|
+
- **Enables infinite loops**: Essential for long-running workers
|
|
741
|
+
|
|
642
742
|
### ASGI Integration
|
|
643
743
|
|
|
644
744
|
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=Budi0FyxalmcAMwj50mX3WzRce5OuLKXGws0Hp_snfw,34745
|
|
5
|
+
edda/compensation.py,sha256=iKLlnTxiF1YSatmYQW84EkPB1yMKUEZBtgjuGnghLtY,11824
|
|
6
|
+
edda/context.py,sha256=IavmrbCdTAozP4QWlQ5-rCHR9yJAT-aohqyrOnbVLBU,20858
|
|
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=vUwjiAOvp9uFNQgLK57kEGo7uzXplDZikOfnlOyed2M,12146
|
|
29
|
+
edda/storage/protocol.py,sha256=NTUuLZ5_OlBiASaJIRuz5x7NykpCOjQgDWWNrRQzong,39021
|
|
30
|
+
edda/storage/sqlalchemy_storage.py,sha256=KvSGapeKJ3hhClXNxFKHByD3Key5aidxBMUjs6-EJvE,136811
|
|
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.9.0.dist-info/METADATA,sha256=esgoKFgUTWqAZWIHxgtKGl5j8VTaWiJw_oz93Dtm064,35741
|
|
40
|
+
edda_framework-0.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
41
|
+
edda_framework-0.9.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
|
|
42
|
+
edda_framework-0.9.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
|
|
43
|
+
edda_framework-0.9.0.dist-info/RECORD,,
|