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/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 ["running", "waiting_for_event", "waiting_for_timer"]:
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."""
@@ -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
- print(f"Warning: Could not get source for {workflow_name}: {e}")
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
- print(f"[Cancel] Attempting to cancel workflow: {instance_id}")
336
- print(f"[Cancel] API URL: {edda_app_url}/cancel/{instance_id}")
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
- print(f"[Cancel] Response status: {response.status_code}")
345
- print(f"[Cancel] Response body: {response.text}")
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
- print(f"[Cancel] Connection error: {e}")
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
- print(f"[Cancel] Timeout error: {e}")
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
- print(f"[Cancel] Unexpected error: {e}")
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
- print(f"Warning: Failed to generate JSON Schema for {annotation}: {e}")
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
- print(f"[StartWorkflow] Attempting to start workflow: {workflow_name}")
860
- print(f"[StartWorkflow] Sending CloudEvent to: {edda_app_url}")
861
- print(f"[StartWorkflow] Params: {params}")
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
- print(f"[StartWorkflow] CloudEvent ID: {attributes['id']}")
880
- print(f"[StartWorkflow] CloudEvent type: {workflow_name}")
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
- print(f"[StartWorkflow] Response status: {response.status_code}")
892
- print(f"[StartWorkflow] Response body: {response.text}")
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
- print(f"[StartWorkflow] Connection error: {e}")
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
- print(f"[StartWorkflow] Timeout error: {e}")
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
- print(f"[StartWorkflow] Unexpected error: {e}")
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.7.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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
66
66
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
67
67
  [![Documentation](https://img.shields.io/badge/docs-latest-green.svg)](https://i2y.github.io/edda/)
68
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](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
- - `wait_timer(duration_seconds)`: Wait for a relative duration
111
- - `wait_until(until_time)`: Wait until an absolute datetime (e.g., campaign end date)
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 wait_timer(ctx, duration_seconds=3*24*60*60) # Wait 3 days
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 `wait_timer()` free up worker resources while waiting, resume on any worker when event arrives or timer expires
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
- **wait_timer() for time-based waiting**:
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 wait_timer
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 wait_timer(ctx, duration_seconds=60)
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=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=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=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,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=usVPKLV4H1-LA6QC72_gkfYrG9T8AwkcmCXppBo-2gQ,7338
29
- edda/storage/protocol.py,sha256=j1YNW1TepuLvPvn7wCcPVIehs0obD30cGOMcyqMwtlA,25852
30
- edda/storage/sqlalchemy_storage.py,sha256=dZ0ulDp6CuZEtDzkD0g76x4ew-dseLWRXrULVsW_isk,76258
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=Zf-ZMDWhmaTuKGJbb27NTQoLhOXMlF-aqRJ7VsC9BwA,95502
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=IK5I9Y2eho5VJz63DhBKuTEsS3LqkeV24Ctc75OGxDM,36478
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.7.0.dist-info/METADATA,sha256=d2PVbDx-LymunfaAs13Hvxb1U8dOf-NR4VmX6whrrP0,31794
40
- edda_framework-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
41
- edda_framework-0.7.0.dist-info/entry_points.txt,sha256=dPH47s6UoJgUZxHoeSMqZsQkLaSE-SGLi-gh88k2WrU,48
42
- edda_framework-0.7.0.dist-info/licenses/LICENSE,sha256=udxb-V7_cYKTHqW7lNm48rxJ-Zpf0WAY_PyGDK9BPCo,1069
43
- edda_framework-0.7.0.dist-info/RECORD,,
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,,