edda-framework 0.14.0__tar.gz → 0.15.0__tar.gz

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.
Files changed (227) hide show
  1. {edda_framework-0.14.0 → edda_framework-0.15.0}/PKG-INFO +13 -1
  2. {edda_framework-0.14.0 → edda_framework-0.15.0}/README.md +8 -0
  3. edda_framework-0.15.0/docs/integrations/llamaindex.md +213 -0
  4. edda_framework-0.15.0/docs/integrations/pydantic-graph.md +256 -0
  5. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/app.py +6 -21
  6. edda_framework-0.15.0/edda/integrations/graph/__init__.py +58 -0
  7. edda_framework-0.15.0/edda/integrations/graph/context.py +81 -0
  8. edda_framework-0.15.0/edda/integrations/graph/exceptions.py +9 -0
  9. edda_framework-0.15.0/edda/integrations/graph/graph.py +385 -0
  10. edda_framework-0.15.0/edda/integrations/graph/nodes.py +144 -0
  11. edda_framework-0.15.0/edda/integrations/llamaindex/__init__.py +51 -0
  12. edda_framework-0.15.0/edda/integrations/llamaindex/events.py +160 -0
  13. edda_framework-0.15.0/edda/integrations/llamaindex/exceptions.py +15 -0
  14. edda_framework-0.15.0/edda/integrations/llamaindex/workflow.py +306 -0
  15. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/locking.py +12 -37
  16. edda_framework-0.15.0/examples/graph/__init__.py +1 -0
  17. edda_framework-0.15.0/examples/graph/demo_graph.py +198 -0
  18. edda_framework-0.15.0/examples/graph/demo_graph_sleep.py +172 -0
  19. edda_framework-0.15.0/examples/graph/demo_graph_wait_event.py +202 -0
  20. edda_framework-0.15.0/examples/llamaindex/__init__.py +1 -0
  21. edda_framework-0.15.0/examples/llamaindex/demo_workflow.py +143 -0
  22. {edda_framework-0.14.0 → edda_framework-0.15.0}/pyproject.toml +12 -1
  23. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_cross_language_channel.py +637 -0
  24. edda_framework-0.15.0/tests/test_graph_integration.py +420 -0
  25. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_stale_workflow_recovery.py +0 -3
  26. {edda_framework-0.14.0 → edda_framework-0.15.0}/uv.lock +468 -2
  27. {edda_framework-0.14.0 → edda_framework-0.15.0}/.github/workflows/ci.yml +0 -0
  28. {edda_framework-0.14.0 → edda_framework-0.15.0}/.github/workflows/docs.yml +0 -0
  29. {edda_framework-0.14.0 → edda_framework-0.15.0}/.github/workflows/release.yml +0 -0
  30. {edda_framework-0.14.0 → edda_framework-0.15.0}/.gitignore +0 -0
  31. {edda_framework-0.14.0 → edda_framework-0.15.0}/.gitmodules +0 -0
  32. {edda_framework-0.14.0 → edda_framework-0.15.0}/.python-version +0 -0
  33. {edda_framework-0.14.0 → edda_framework-0.15.0}/Justfile +0 -0
  34. {edda_framework-0.14.0 → edda_framework-0.15.0}/LICENSE +0 -0
  35. {edda_framework-0.14.0 → edda_framework-0.15.0}/demo_app.py +0 -0
  36. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/api/reference.md +0 -0
  37. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/durable-execution/replay.md +0 -0
  38. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/events/cloudevents-http-binding.md +0 -0
  39. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/events/postgres-notify.md +0 -0
  40. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/events/wait-event.md +0 -0
  41. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/hooks.md +0 -0
  42. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/messages.md +0 -0
  43. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/retry.md +0 -0
  44. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/saga-compensation.md +0 -0
  45. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/transactional-outbox.md +0 -0
  46. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/core-features/workflows-activities.md +0 -0
  47. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/examples/ecommerce.md +0 -0
  48. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/examples/events.md +0 -0
  49. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/examples/fastapi-integration.md +0 -0
  50. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/examples/saga.md +0 -0
  51. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/examples/simple.md +0 -0
  52. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/getting-started/concepts.md +0 -0
  53. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/getting-started/first-workflow.md +0 -0
  54. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/getting-started/installation.md +0 -0
  55. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/getting-started/quick-start.md +0 -0
  56. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/index.md +0 -0
  57. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/integrations/mcp.md +0 -0
  58. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/integrations/mirascope.md +0 -0
  59. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/integrations/opentelemetry.md +0 -0
  60. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/integrations/pydantic-rpc.md +0 -0
  61. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/cloudevents-cli-trigger.png +0 -0
  62. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/compensation-execution.png +0 -0
  63. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/detail-page-loan-approval.png +0 -0
  64. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/detail-page-match-case.png +0 -0
  65. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/nested-pydantic-form.png +0 -0
  66. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/start-workflow-form-pydantic.png +0 -0
  67. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/wait-event-visualization.png +0 -0
  68. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/workflow-list-view.png +0 -0
  69. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/images/workflow-selection-dropdown.png +0 -0
  70. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/setup.md +0 -0
  71. {edda_framework-0.14.0 → edda_framework-0.15.0}/docs/viewer-ui/visualization.md +0 -0
  72. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/__init__.py +0 -0
  73. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/activity.py +0 -0
  74. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/channels.py +0 -0
  75. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/compensation.py +0 -0
  76. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/context.py +0 -0
  77. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/exceptions.py +0 -0
  78. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/hooks.py +0 -0
  79. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/__init__.py +0 -0
  80. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mcp/__init__.py +0 -0
  81. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mcp/decorators.py +0 -0
  82. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mcp/server.py +0 -0
  83. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mirascope/__init__.py +0 -0
  84. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mirascope/agent.py +0 -0
  85. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mirascope/call.py +0 -0
  86. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mirascope/decorator.py +0 -0
  87. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/mirascope/types.py +0 -0
  88. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/opentelemetry/__init__.py +0 -0
  89. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/integrations/opentelemetry/hooks.py +0 -0
  90. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/migrations/mysql/20251217000000_initial_schema.sql +0 -0
  91. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/migrations/postgresql/20251217000000_initial_schema.sql +0 -0
  92. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/migrations/sqlite/20251217000000_initial_schema.sql +0 -0
  93. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/outbox/__init__.py +0 -0
  94. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/outbox/relayer.py +0 -0
  95. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/outbox/transactional.py +0 -0
  96. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/pydantic_utils.py +0 -0
  97. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/replay.py +0 -0
  98. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/retry.py +0 -0
  99. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/serialization/__init__.py +0 -0
  100. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/serialization/base.py +0 -0
  101. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/serialization/json.py +0 -0
  102. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/__init__.py +0 -0
  103. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/migrations.py +0 -0
  104. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/models.py +0 -0
  105. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/notify_base.py +0 -0
  106. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/pg_notify.py +0 -0
  107. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/protocol.py +0 -0
  108. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/storage/sqlalchemy_storage.py +0 -0
  109. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/viewer_ui/__init__.py +0 -0
  110. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/viewer_ui/app.py +0 -0
  111. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/viewer_ui/components.py +0 -0
  112. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/viewer_ui/data_service.py +0 -0
  113. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/viewer_ui/theme.py +0 -0
  114. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/visualizer/__init__.py +0 -0
  115. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/visualizer/ast_analyzer.py +0 -0
  116. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/visualizer/mermaid_generator.py +0 -0
  117. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/workflow.py +0 -0
  118. {edda_framework-0.14.0 → edda_framework-0.15.0}/edda/wsgi.py +0 -0
  119. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/__init__.py +0 -0
  120. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/cancellable_workflow.py +0 -0
  121. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/compensation_workflow.py +0 -0
  122. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/event_waiting_app.py +0 -0
  123. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/event_waiting_workflow.py +0 -0
  124. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/event_waiting_workflow_complete.py +0 -0
  125. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/long_running_loop.py +0 -0
  126. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mcp/README.md +0 -0
  127. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mcp/order_processing_mcp.py +0 -0
  128. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mcp/prompts_example.py +0 -0
  129. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mcp/remote_server_example.py +0 -0
  130. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mcp/simple_mcp_server.py +0 -0
  131. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/message_passing.py +0 -0
  132. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mirascope/__init__.py +0 -0
  133. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mirascope/durable_agent.py +0 -0
  134. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mirascope/multi_turn.py +0 -0
  135. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mirascope/simple_call.py +0 -0
  136. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/mirascope/with_tools.py +0 -0
  137. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/observability_with_logfire.py +0 -0
  138. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/observability_with_opentelemetry.py +0 -0
  139. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/pydantic_rpc_integration.py +0 -0
  140. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/pydantic_saga.py +0 -0
  141. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/retry_example.py +0 -0
  142. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/retry_with_compensation.py +0 -0
  143. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/simple_workflow.py +0 -0
  144. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/typeddict_example.py +0 -0
  145. {edda_framework-0.14.0 → edda_framework-0.15.0}/examples/with_outbox.py +0 -0
  146. {edda_framework-0.14.0 → edda_framework-0.15.0}/schema/.dbmate.yml +0 -0
  147. {edda_framework-0.14.0 → edda_framework-0.15.0}/schema/.git +0 -0
  148. {edda_framework-0.14.0 → edda_framework-0.15.0}/schema/.gitignore +0 -0
  149. {edda_framework-0.14.0 → edda_framework-0.15.0}/schema/LICENSE +0 -0
  150. {edda_framework-0.14.0 → edda_framework-0.15.0}/schema/README.md +0 -0
  151. {edda_framework-0.14.0 → edda_framework-0.15.0}/schema/docs/column-values.md +0 -0
  152. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/__init__.py +0 -0
  153. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/conftest.py +0 -0
  154. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/__init__.py +0 -0
  155. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mcp/__init__.py +0 -0
  156. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mcp/test_cancel.py +0 -0
  157. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mcp/test_integration.py +0 -0
  158. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mcp/test_jsonrpc.py +0 -0
  159. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mcp/test_prompts.py +0 -0
  160. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mcp/test_server.py +0 -0
  161. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mirascope/__init__.py +0 -0
  162. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mirascope/test_agent.py +0 -0
  163. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mirascope/test_call.py +0 -0
  164. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mirascope/test_decorator.py +0 -0
  165. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/mirascope/test_types.py +0 -0
  166. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/opentelemetry/__init__.py +0 -0
  167. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/integrations/opentelemetry/test_hooks.py +0 -0
  168. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_activity.py +0 -0
  169. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_activity_retry.py +0 -0
  170. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_activity_sync.py +0 -0
  171. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_app.py +0 -0
  172. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_ast_analyzer.py +0 -0
  173. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_atomic_wait_event.py +0 -0
  174. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_auto_migration.py +0 -0
  175. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_binary_data.py +0 -0
  176. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_channel_competing.py +0 -0
  177. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_channel_direct.py +0 -0
  178. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_channel_mode_locking.py +0 -0
  179. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_channel_transactional.py +0 -0
  180. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_cloudevents_http_binding.py +0 -0
  181. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_compensation.py +0 -0
  182. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_compensation_crash_recovery.py.wip +0 -0
  183. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_concurrent_outbox.py +0 -0
  184. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_context.py +0 -0
  185. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_ctx_session.py +0 -0
  186. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_distributed_event_delivery.py +0 -0
  187. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_events.py +0 -0
  188. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_instance_id_routing.py +0 -0
  189. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_lock_race_condition.py +0 -0
  190. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_lock_timeout_customization.py +0 -0
  191. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_locking.py +0 -0
  192. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_message_cleanup.py +0 -0
  193. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_message_delivery_lock.py +0 -0
  194. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_messages.py +0 -0
  195. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_migrations_integration.py +0 -0
  196. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_multidb_storage.py +0 -0
  197. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_outbox.py +0 -0
  198. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_pg_notify.py +0 -0
  199. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_polling_optimization.py +0 -0
  200. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_pydantic_activity.py +0 -0
  201. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_pydantic_enum.py +0 -0
  202. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_pydantic_events.py +0 -0
  203. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_pydantic_saga.py +0 -0
  204. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_pydantic_utils.py +0 -0
  205. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_receive_timeout.py +0 -0
  206. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_received_event.py +0 -0
  207. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_recur.py +0 -0
  208. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_recur_cleanup.py +0 -0
  209. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_replay.py +0 -0
  210. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_retry_policy.py +0 -0
  211. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_saga_parameter_extraction.py +0 -0
  212. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_serialization.py +0 -0
  213. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_skip_locked.py +0 -0
  214. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_storage.py +0 -0
  215. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_storage_mysql.py +0 -0
  216. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_storage_postgresql.py +0 -0
  217. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_transactions.py +0 -0
  218. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_viewer_pagination.py +0 -0
  219. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_viewer_pydantic_form.py +0 -0
  220. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_viewer_start_saga.py +0 -0
  221. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_wait_timer.py +0 -0
  222. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_workflow.py +0 -0
  223. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_workflow_auto_register.py +0 -0
  224. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_workflow_cancellation.py +0 -0
  225. {edda_framework-0.14.0 → edda_framework-0.15.0}/tests/test_workflow_resumption.py +0 -0
  226. {edda_framework-0.14.0 → edda_framework-0.15.0}/viewer_app.py +0 -0
  227. {edda_framework-0.14.0 → edda_framework-0.15.0}/zensical.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.14.0
3
+ Version: 0.15.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
@@ -42,6 +42,10 @@ Requires-Dist: starlette>=0.40.0; extra == 'dev'
42
42
  Requires-Dist: testcontainers[mysql]>=4.0.0; extra == 'dev'
43
43
  Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
44
44
  Requires-Dist: tsuno>=0.1.3; extra == 'dev'
45
+ Provides-Extra: graph
46
+ Requires-Dist: pydantic-graph>=0.1.0; extra == 'graph'
47
+ Provides-Extra: llamaindex
48
+ Requires-Dist: llama-index-core>=0.12.0; extra == 'llamaindex'
45
49
  Provides-Extra: mcp
46
50
  Requires-Dist: mcp>=1.22.0; extra == 'mcp'
47
51
  Provides-Extra: mirascope
@@ -97,6 +101,8 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
97
101
  - ⚡ **Instant Notifications**: PostgreSQL LISTEN/NOTIFY for near-instant event delivery (optional)
98
102
  - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
99
103
  - 🧠 **Mirascope Integration**: Durable LLM calls
104
+ - 🦙 **LlamaIndex Integration**: Make LlamaIndex Workflows durable with crash recovery
105
+ - 📊 **pydantic-graph Integration**: Durable graph-based workflows (experimental)
100
106
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
101
107
 
102
108
  ## Use Cases
@@ -233,6 +239,12 @@ uv add edda-framework --extra viewer
233
239
  # With PostgreSQL instant notifications (LISTEN/NOTIFY)
234
240
  uv add edda-framework --extra postgres-notify
235
241
 
242
+ # With LlamaIndex Workflow integration
243
+ uv add edda-framework --extra llamaindex
244
+
245
+ # With pydantic-graph integration (experimental)
246
+ uv add edda-framework --extra graph
247
+
236
248
  # All extras (PostgreSQL, MySQL, Viewer UI)
237
249
  uv add edda-framework --extra postgresql --extra mysql --extra viewer
238
250
  ```
@@ -32,6 +32,8 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
32
32
  - ⚡ **Instant Notifications**: PostgreSQL LISTEN/NOTIFY for near-instant event delivery (optional)
33
33
  - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
34
34
  - 🧠 **Mirascope Integration**: Durable LLM calls
35
+ - 🦙 **LlamaIndex Integration**: Make LlamaIndex Workflows durable with crash recovery
36
+ - 📊 **pydantic-graph Integration**: Durable graph-based workflows (experimental)
35
37
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
36
38
 
37
39
  ## Use Cases
@@ -168,6 +170,12 @@ uv add edda-framework --extra viewer
168
170
  # With PostgreSQL instant notifications (LISTEN/NOTIFY)
169
171
  uv add edda-framework --extra postgres-notify
170
172
 
173
+ # With LlamaIndex Workflow integration
174
+ uv add edda-framework --extra llamaindex
175
+
176
+ # With pydantic-graph integration (experimental)
177
+ uv add edda-framework --extra graph
178
+
171
179
  # All extras (PostgreSQL, MySQL, Viewer UI)
172
180
  uv add edda-framework --extra postgresql --extra mysql --extra viewer
173
181
  ```
@@ -0,0 +1,213 @@
1
+ # LlamaIndex Workflow Integration
2
+
3
+ Edda provides integration with [LlamaIndex Workflow](https://docs.llamaindex.ai/en/stable/module_guides/workflow/), making your LlamaIndex Workflows **durable**. Each workflow step is automatically recorded as an Edda Activity, enabling crash recovery and deterministic replay.
4
+
5
+ ## Overview
6
+
7
+ LlamaIndex Workflow is an event-driven orchestration framework for building complex AI pipelines. Edda's integration adds:
8
+
9
+ - **Crash Recovery**: If your workflow crashes, completed steps are replayed from cache
10
+ - **Durable Timers**: Use `DurableSleepEvent` for timers that survive crashes
11
+ - **Durable Event Waiting**: Use `DurableWaitEvent` to wait for external events durably
12
+ - **Step-level Durability**: Each `@step` becomes a durable Edda Activity
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install 'edda-framework[llamaindex]'
18
+
19
+ # Or using uv
20
+ uv add edda-framework --extra llamaindex
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ from llama_index.core.workflow import Workflow, step, StartEvent, StopEvent, Event
27
+ from edda import EddaApp, workflow, WorkflowContext
28
+ from edda.integrations.llamaindex import DurableWorkflowRunner
29
+
30
+ # Define events
31
+ class OrderReceivedEvent(Event):
32
+ order_id: str
33
+ amount: float
34
+
35
+ class ProcessingCompleteEvent(Event):
36
+ order_id: str
37
+ status: str
38
+
39
+ # Define LlamaIndex Workflow
40
+ class OrderWorkflow(Workflow):
41
+ @step
42
+ async def receive_order(self, ctx, ev: StartEvent) -> OrderReceivedEvent:
43
+ print(f"Received order: {ev.order_id}")
44
+ return OrderReceivedEvent(order_id=ev.order_id, amount=ev.amount)
45
+
46
+ @step
47
+ async def process_order(self, ctx, ev: OrderReceivedEvent) -> ProcessingCompleteEvent:
48
+ print(f"Processing order {ev.order_id}...")
49
+ return ProcessingCompleteEvent(order_id=ev.order_id, status="processed")
50
+
51
+ @step
52
+ async def complete_order(self, ctx, ev: ProcessingCompleteEvent) -> StopEvent:
53
+ return StopEvent(result={
54
+ "order_id": ev.order_id,
55
+ "status": ev.status,
56
+ "message": "Order completed successfully",
57
+ })
58
+
59
+ # Create durable runner
60
+ runner = DurableWorkflowRunner(OrderWorkflow)
61
+
62
+ # Wrap in Edda workflow
63
+ @workflow
64
+ async def order_workflow(ctx: WorkflowContext, order_id: str, amount: float) -> dict:
65
+ result = await runner.run(ctx, order_id=order_id, amount=amount)
66
+ return result
67
+
68
+ # Run the workflow
69
+ async def main():
70
+ app = EddaApp(service_name="order-service", db_url="sqlite:///app.db")
71
+ await app.initialize()
72
+
73
+ instance_id = await order_workflow.start(order_id="ORD-001", amount=99.99)
74
+ print(f"Started workflow: {instance_id}")
75
+
76
+ await app.shutdown()
77
+ ```
78
+
79
+ ## How It Works
80
+
81
+ ```
82
+ LlamaIndex Workflow Steps
83
+
84
+
85
+ ┌──────────────────────┐
86
+ │ DurableWorkflowRunner│ ← Wraps your Workflow class
87
+ └──────────┬───────────┘
88
+
89
+
90
+ ┌──────────────────────┐
91
+ │ For each @step: │
92
+ │ - Serialize event │
93
+ │ - Run as Activity │
94
+ │ - Deserialize result│
95
+ └──────────┬───────────┘
96
+
97
+
98
+ ┌──────────────────────┐
99
+ │ Edda @activity │ ← Each step is durable
100
+ │ (_run_step) │
101
+ └──────────────────────┘
102
+ ```
103
+
104
+ **On Replay**: If the workflow crashes and restarts, completed steps return cached results from the Edda history. Steps are not re-executed.
105
+
106
+ ## Durable Sleep
107
+
108
+ Use `DurableSleepEvent` for timers that survive crashes:
109
+
110
+ ```python
111
+ from edda.integrations.llamaindex import DurableSleepEvent
112
+
113
+ class RateLimitedWorkflow(Workflow):
114
+ @step
115
+ async def check_rate_limit(self, ctx, ev: StartEvent) -> DurableSleepEvent | ProcessEvent:
116
+ if is_rate_limited():
117
+ # Sleep for 60 seconds (durable timer)
118
+ return DurableSleepEvent(
119
+ seconds=60,
120
+ resume_data={"retry_count": ev.retry_count + 1}
121
+ )
122
+ return ProcessEvent(data=ev.data)
123
+
124
+ @step
125
+ async def handle_resume(self, ctx, ev: ResumeEvent) -> ProcessEvent:
126
+ # Called after DurableSleepEvent completes
127
+ retry_count = ev.data.get("retry_count", 0)
128
+ return ProcessEvent(data={"retried": retry_count})
129
+ ```
130
+
131
+ ## Durable Wait for External Events
132
+
133
+ Use `DurableWaitEvent` to wait for external events:
134
+
135
+ ```python
136
+ from edda.integrations.llamaindex import DurableWaitEvent, ResumeEvent
137
+
138
+ class ApprovalWorkflow(Workflow):
139
+ @step
140
+ async def request_approval(self, ctx, ev: StartEvent) -> DurableWaitEvent:
141
+ # Wait for approval event (up to 1 hour)
142
+ return DurableWaitEvent(
143
+ event_type=f"approval.{ev.request_id}",
144
+ timeout_seconds=3600,
145
+ )
146
+
147
+ @step
148
+ async def process_approval(self, ctx, ev: ResumeEvent) -> StopEvent:
149
+ # Called when external event arrives
150
+ approved = ev.data.get("approved", False)
151
+ return StopEvent(result={"approved": approved})
152
+ ```
153
+
154
+ To send the external event:
155
+
156
+ ```python
157
+ from edda import send_event
158
+
159
+ await send_event(
160
+ event_type="approval.REQ-123",
161
+ source="approval-service",
162
+ data={"approved": True},
163
+ )
164
+ ```
165
+
166
+ ## API Reference
167
+
168
+ ### DurableWorkflowRunner
169
+
170
+ ```python
171
+ from edda.integrations.llamaindex import DurableWorkflowRunner
172
+
173
+ runner = DurableWorkflowRunner(MyWorkflow)
174
+ result = await runner.run(ctx, **start_event_kwargs)
175
+ ```
176
+
177
+ | Parameter | Type | Description |
178
+ |-----------|------|-------------|
179
+ | `workflow_class` | `type[Workflow]` | LlamaIndex Workflow class (not instance) |
180
+
181
+ ### DurableSleepEvent
182
+
183
+ ```python
184
+ DurableSleepEvent(
185
+ seconds: float, # Duration to sleep
186
+ resume_data: dict = None, # Data passed to ResumeEvent
187
+ )
188
+ ```
189
+
190
+ ### DurableWaitEvent
191
+
192
+ ```python
193
+ DurableWaitEvent(
194
+ event_type: str, # Event type to wait for
195
+ timeout_seconds: float = None, # Optional timeout
196
+ )
197
+ ```
198
+
199
+ ### ResumeEvent
200
+
201
+ Returned after `DurableSleepEvent` or `DurableWaitEvent` completes:
202
+
203
+ ```python
204
+ class ResumeEvent:
205
+ data: dict # Contains resume_data or received event data
206
+ ```
207
+
208
+ ## Related Documentation
209
+
210
+ - [Workflows and Activities](../core-features/workflows-activities.md) - Core concepts of durable execution
211
+ - [Event Waiting](../core-features/events/wait-event.md) - wait_event() function
212
+ - [LlamaIndex Workflow Docs](https://docs.llamaindex.ai/en/stable/module_guides/workflow/) - Official LlamaIndex documentation
213
+ - [Examples](https://github.com/i2y/edda/tree/main/examples/llamaindex) - Complete working examples
@@ -0,0 +1,256 @@
1
+ # pydantic-graph Integration (Experimental)
2
+
3
+ > **Status: Experimental**
4
+ > This integration is functional but has known limitations with type hints. The API may change in future versions.
5
+
6
+ Edda provides integration with [pydantic-graph](https://ai.pydantic.dev/pydantic-graph/), making graph-based workflows **durable**. Each node execution is automatically recorded as an Edda Activity, enabling crash recovery and deterministic replay.
7
+
8
+ ## Overview
9
+
10
+ pydantic-graph is a graph-based workflow library that uses dataclass nodes and type-safe transitions. Edda's integration adds:
11
+
12
+ - **Crash Recovery**: If your workflow crashes, completed nodes are replayed from cache
13
+ - **Durable Timers**: Use `Sleep` marker for timers that survive crashes
14
+ - **Durable Event Waiting**: Use `WaitForEvent` marker to wait for external events durably
15
+ - **Node-level Durability**: Each node execution becomes a durable Edda Activity
16
+
17
+ ## Known Limitations
18
+
19
+ **Type hint limitation**: The `Sleep` and `WaitForEvent` markers cannot be included in node return type annotations due to pydantic-graph's strict type validation. You need to use `# type: ignore` comments:
20
+
21
+ ```python
22
+ # This works at runtime but requires type: ignore
23
+ async def run(self, ctx: DurableGraphContext) -> "NextNode": # type: ignore[override]
24
+ return Sleep(seconds=60, next_node=NextNode())
25
+ ```
26
+
27
+ If you need clean type hints for graph visualization, consider the [LlamaIndex Workflow integration](./llamaindex.md) instead.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install 'edda-framework[graph]'
33
+
34
+ # Or using uv
35
+ uv add edda-framework --extra graph
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from dataclasses import dataclass
42
+ from pydantic_graph import BaseNode, End, Graph
43
+ from edda import EddaApp, workflow, WorkflowContext
44
+ from edda.integrations.graph import DurableGraph, DurableGraphContext
45
+
46
+ # Define state
47
+ @dataclass
48
+ class OrderState:
49
+ order_id: str | None = None
50
+ total: float = 0.0
51
+ status: str = "pending"
52
+
53
+ # Define nodes
54
+ @dataclass
55
+ class ValidateOrderNode(BaseNode[OrderState, None, dict]):
56
+ order_id: str
57
+ amount: float
58
+
59
+ async def run(self, ctx: DurableGraphContext) -> "ProcessPaymentNode":
60
+ ctx.state.order_id = self.order_id
61
+ ctx.state.total = self.amount
62
+ ctx.state.status = "validated"
63
+ return ProcessPaymentNode()
64
+
65
+ @dataclass
66
+ class ProcessPaymentNode(BaseNode[OrderState, None, dict]):
67
+ async def run(self, ctx: DurableGraphContext) -> End[dict]:
68
+ ctx.state.status = "paid"
69
+ return End({
70
+ "order_id": ctx.state.order_id,
71
+ "total": ctx.state.total,
72
+ "status": ctx.state.status,
73
+ })
74
+
75
+ # Create graph and durable wrapper
76
+ graph = Graph(nodes=[ValidateOrderNode, ProcessPaymentNode])
77
+ durable_graph = DurableGraph(graph)
78
+
79
+ # Wrap in Edda workflow
80
+ @workflow
81
+ async def order_workflow(ctx: WorkflowContext, order_id: str, amount: float) -> dict:
82
+ return await durable_graph.run(
83
+ ctx,
84
+ start_node=ValidateOrderNode(order_id=order_id, amount=amount),
85
+ state=OrderState(),
86
+ )
87
+
88
+ # Run the workflow
89
+ async def main():
90
+ app = EddaApp(service_name="order-service", db_url="sqlite:///app.db")
91
+ await app.initialize()
92
+
93
+ instance_id = await order_workflow.start(order_id="ORD-001", amount=99.99)
94
+ print(f"Started workflow: {instance_id}")
95
+
96
+ await app.shutdown()
97
+ ```
98
+
99
+ ## How It Works
100
+
101
+ ```
102
+ pydantic-graph Nodes
103
+
104
+
105
+ ┌──────────────────────┐
106
+ │ DurableGraph │ ← Wraps your Graph
107
+ └──────────┬───────────┘
108
+
109
+
110
+ ┌──────────────────────┐
111
+ │ For each node: │
112
+ │ - Serialize state │
113
+ │ - Run as Activity │
114
+ │ - Deserialize result│
115
+ └──────────┬───────────┘
116
+
117
+
118
+ ┌──────────────────────┐
119
+ │ Edda @activity │ ← Each node is durable
120
+ │ (_run_graph_node) │
121
+ └──────────────────────┘
122
+ ```
123
+
124
+ **On Replay**: If the workflow crashes and restarts, completed nodes return cached results from the Edda history. Nodes are not re-executed.
125
+
126
+ ## Durable Sleep
127
+
128
+ Use the `Sleep` marker for timers that survive crashes:
129
+
130
+ ```python
131
+ from edda.integrations.graph import Sleep
132
+
133
+ @dataclass
134
+ class RateLimitNode(BaseNode[MyState, None, str]):
135
+ async def run(self, ctx: DurableGraphContext) -> "RetryNode": # type: ignore[override]
136
+ if is_rate_limited():
137
+ # Sleep for 60 seconds (durable timer)
138
+ return Sleep(seconds=60, next_node=RetryNode())
139
+ return RetryNode()
140
+ ```
141
+
142
+ ## Durable Wait for External Events
143
+
144
+ Use the `WaitForEvent` marker to wait for external events:
145
+
146
+ ```python
147
+ from edda.integrations.graph import WaitForEvent, ReceivedEvent
148
+
149
+ @dataclass
150
+ class WaitForApprovalNode(BaseNode[OrderState, None, str]):
151
+ async def run(self, ctx: DurableGraphContext) -> "ProcessApprovalNode": # type: ignore[override]
152
+ return WaitForEvent(
153
+ event_type=f"approval.{ctx.state.order_id}",
154
+ next_node=ProcessApprovalNode(),
155
+ timeout_seconds=3600,
156
+ )
157
+
158
+ @dataclass
159
+ class ProcessApprovalNode(BaseNode[OrderState, None, str]):
160
+ async def run(self, ctx: DurableGraphContext) -> End[str]:
161
+ # Access the received event via ctx.last_event
162
+ event: ReceivedEvent = ctx.last_event
163
+ if event.data.get("approved"):
164
+ return End("approved")
165
+ return End("rejected")
166
+ ```
167
+
168
+ To send the external event:
169
+
170
+ ```python
171
+ from edda import send_event
172
+
173
+ await send_event(
174
+ event_type="approval.ORD-123",
175
+ source="approval-service",
176
+ data={"approved": True},
177
+ )
178
+ ```
179
+
180
+ ## DurableGraphContext
181
+
182
+ The `DurableGraphContext` provides access to state, dependencies, and received events:
183
+
184
+ ```python
185
+ @dataclass
186
+ class MyNode(BaseNode[MyState, MyDeps, str]):
187
+ async def run(self, ctx: DurableGraphContext) -> End[str]:
188
+ # Access state (mutable)
189
+ ctx.state.counter += 1
190
+
191
+ # Access dependencies (read-only)
192
+ api_key = ctx.deps.api_key
193
+
194
+ # Access last received event (after WaitForEvent)
195
+ if ctx.last_event:
196
+ data = ctx.last_event.data
197
+
198
+ # Access Edda WorkflowContext for advanced operations
199
+ edda_ctx = ctx.workflow_ctx
200
+
201
+ return End("done")
202
+ ```
203
+
204
+ ## API Reference
205
+
206
+ ### DurableGraph
207
+
208
+ ```python
209
+ from edda.integrations.graph import DurableGraph
210
+
211
+ durable = DurableGraph(graph)
212
+ result = await durable.run(ctx, start_node=MyNode(), state=MyState(), deps=MyDeps())
213
+ ```
214
+
215
+ | Parameter | Type | Description |
216
+ |-----------|------|-------------|
217
+ | `graph` | `Graph` | pydantic-graph Graph instance |
218
+
219
+ ### Sleep
220
+
221
+ ```python
222
+ Sleep(
223
+ seconds: int, # Duration to sleep
224
+ next_node: NextT, # Node to continue with after sleep
225
+ )
226
+ ```
227
+
228
+ ### WaitForEvent
229
+
230
+ ```python
231
+ WaitForEvent(
232
+ event_type: str, # Event type to wait for
233
+ next_node: NextT, # Node to continue with after event
234
+ timeout_seconds: int | None, # Optional timeout
235
+ )
236
+ ```
237
+
238
+ ### ReceivedEvent
239
+
240
+ Available via `ctx.last_event` after `WaitForEvent` completes:
241
+
242
+ ```python
243
+ @dataclass
244
+ class ReceivedEvent:
245
+ event_type: str # The event type that was received
246
+ data: dict[str, Any] # Event payload
247
+ metadata: dict[str, Any] # Event metadata
248
+ ```
249
+
250
+ ## Related Documentation
251
+
252
+ - [Workflows and Activities](../core-features/workflows-activities.md) - Core concepts of durable execution
253
+ - [Event Waiting](../core-features/events/wait-event.md) - wait_event() function
254
+ - [LlamaIndex Integration](./llamaindex.md) - Alternative with better type hint support
255
+ - [pydantic-graph Documentation](https://ai.pydantic.dev/pydantic-graph/) - Official pydantic-graph docs
256
+ - [Examples](https://github.com/i2y/edda/tree/main/examples/graph) - Complete working examples
@@ -583,7 +583,6 @@ class EddaApp:
583
583
  auto_resume_stale_workflows_periodically(
584
584
  self.storage,
585
585
  self.replay_engine,
586
- self.worker_id,
587
586
  interval=60,
588
587
  ),
589
588
  name="leader_stale_workflow_resume",
@@ -628,7 +627,6 @@ class EddaApp:
628
627
  auto_resume_stale_workflows_periodically(
629
628
  self.storage,
630
629
  self.replay_engine,
631
- self.worker_id,
632
630
  interval=60,
633
631
  ),
634
632
  name="leader_stale_workflow_resume",
@@ -1411,7 +1409,8 @@ class EddaApp:
1411
1409
  from growing indefinitely with orphaned messages (messages that were
1412
1410
  published but never received by any subscriber).
1413
1411
 
1414
- Uses system-level locking to ensure only one pod executes cleanup at a time.
1412
+ Important: This task should only be run by a single worker (e.g., via leader
1413
+ election). It does not perform its own distributed coordination.
1415
1414
 
1416
1415
  Args:
1417
1416
  interval: Cleanup interval in seconds (default: 3600 = 1 hour)
@@ -1422,27 +1421,13 @@ class EddaApp:
1422
1421
  """
1423
1422
  while True:
1424
1423
  try:
1425
- # Add jitter to prevent thundering herd in multi-pod deployments
1424
+ # Add jitter to prevent thundering herd
1426
1425
  jitter = random.uniform(0, interval * 0.3)
1427
1426
  await asyncio.sleep(interval + jitter)
1428
1427
 
1429
- # Try to acquire global lock for this task
1430
- lock_acquired = await self.storage.try_acquire_system_lock(
1431
- lock_name="cleanup_old_messages",
1432
- worker_id=self.worker_id,
1433
- timeout_seconds=interval,
1434
- )
1435
-
1436
- if not lock_acquired:
1437
- # Another pod is handling this task
1438
- continue
1439
-
1440
- try:
1441
- deleted_count = await self.storage.cleanup_old_channel_messages(retention_days)
1442
- if deleted_count > 0:
1443
- logger.info("Cleaned up %d old channel messages", deleted_count)
1444
- finally:
1445
- await self.storage.release_system_lock("cleanup_old_messages", self.worker_id)
1428
+ deleted_count = await self.storage.cleanup_old_channel_messages(retention_days)
1429
+ if deleted_count > 0:
1430
+ logger.info("Cleaned up %d old channel messages", deleted_count)
1446
1431
  except Exception as e:
1447
1432
  logger.error("Error cleaning up old messages: %s", e, exc_info=True)
1448
1433
 
@@ -0,0 +1,58 @@
1
+ """
2
+ Durable Graph Integration for Edda.
3
+
4
+ This module provides integration between pydantic-graph and Edda's durable
5
+ execution framework, making pydantic-graph execution crash-recoverable and
6
+ supporting durable wait operations.
7
+
8
+ Example:
9
+ from dataclasses import dataclass
10
+ from pydantic_graph import BaseNode, Graph, End
11
+ from edda import workflow, WorkflowContext
12
+ from edda.integrations.graph import DurableGraph, DurableGraphContext
13
+
14
+ @dataclass
15
+ class MyState:
16
+ counter: int = 0
17
+
18
+ @dataclass
19
+ class IncrementNode(BaseNode[MyState, None, int]):
20
+ async def run(self, ctx: DurableGraphContext) -> "CheckNode":
21
+ ctx.state.counter += 1
22
+ return CheckNode()
23
+
24
+ @dataclass
25
+ class CheckNode(BaseNode[MyState, None, int]):
26
+ async def run(self, ctx: DurableGraphContext) -> IncrementNode | End[int]:
27
+ if ctx.state.counter >= 5:
28
+ return End(ctx.state.counter)
29
+ return IncrementNode()
30
+
31
+ graph = Graph(nodes=[IncrementNode, CheckNode])
32
+ durable = DurableGraph(graph)
33
+
34
+ @workflow
35
+ async def counter_workflow(ctx: WorkflowContext) -> int:
36
+ return await durable.run(
37
+ ctx,
38
+ start_node=IncrementNode(),
39
+ state=MyState(),
40
+ )
41
+
42
+ Installation:
43
+ pip install 'edda-framework[graph]'
44
+ """
45
+
46
+ from .context import DurableGraphContext
47
+ from .exceptions import GraphExecutionError
48
+ from .graph import DurableGraph
49
+ from .nodes import ReceivedEvent, Sleep, WaitForEvent
50
+
51
+ __all__ = [
52
+ "DurableGraph",
53
+ "DurableGraphContext",
54
+ "GraphExecutionError",
55
+ "ReceivedEvent",
56
+ "Sleep",
57
+ "WaitForEvent",
58
+ ]