edda-framework 0.14.1__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.1 → edda_framework-0.15.0}/PKG-INFO +13 -1
  2. {edda_framework-0.14.1 → 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.15.0/edda/integrations/graph/__init__.py +58 -0
  6. edda_framework-0.15.0/edda/integrations/graph/context.py +81 -0
  7. edda_framework-0.15.0/edda/integrations/graph/exceptions.py +9 -0
  8. edda_framework-0.15.0/edda/integrations/graph/graph.py +385 -0
  9. edda_framework-0.15.0/edda/integrations/graph/nodes.py +144 -0
  10. edda_framework-0.15.0/edda/integrations/llamaindex/__init__.py +51 -0
  11. edda_framework-0.15.0/edda/integrations/llamaindex/events.py +160 -0
  12. edda_framework-0.15.0/edda/integrations/llamaindex/exceptions.py +15 -0
  13. edda_framework-0.15.0/edda/integrations/llamaindex/workflow.py +306 -0
  14. edda_framework-0.15.0/examples/graph/__init__.py +1 -0
  15. edda_framework-0.15.0/examples/graph/demo_graph.py +198 -0
  16. edda_framework-0.15.0/examples/graph/demo_graph_sleep.py +172 -0
  17. edda_framework-0.15.0/examples/graph/demo_graph_wait_event.py +202 -0
  18. edda_framework-0.15.0/examples/llamaindex/__init__.py +1 -0
  19. edda_framework-0.15.0/examples/llamaindex/demo_workflow.py +143 -0
  20. {edda_framework-0.14.1 → edda_framework-0.15.0}/pyproject.toml +12 -1
  21. edda_framework-0.15.0/tests/test_graph_integration.py +420 -0
  22. {edda_framework-0.14.1 → edda_framework-0.15.0}/uv.lock +468 -2
  23. {edda_framework-0.14.1 → edda_framework-0.15.0}/.github/workflows/ci.yml +0 -0
  24. {edda_framework-0.14.1 → edda_framework-0.15.0}/.github/workflows/docs.yml +0 -0
  25. {edda_framework-0.14.1 → edda_framework-0.15.0}/.github/workflows/release.yml +0 -0
  26. {edda_framework-0.14.1 → edda_framework-0.15.0}/.gitignore +0 -0
  27. {edda_framework-0.14.1 → edda_framework-0.15.0}/.gitmodules +0 -0
  28. {edda_framework-0.14.1 → edda_framework-0.15.0}/.python-version +0 -0
  29. {edda_framework-0.14.1 → edda_framework-0.15.0}/Justfile +0 -0
  30. {edda_framework-0.14.1 → edda_framework-0.15.0}/LICENSE +0 -0
  31. {edda_framework-0.14.1 → edda_framework-0.15.0}/demo_app.py +0 -0
  32. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/api/reference.md +0 -0
  33. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/durable-execution/replay.md +0 -0
  34. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/events/cloudevents-http-binding.md +0 -0
  35. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/events/postgres-notify.md +0 -0
  36. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/events/wait-event.md +0 -0
  37. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/hooks.md +0 -0
  38. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/messages.md +0 -0
  39. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/retry.md +0 -0
  40. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/saga-compensation.md +0 -0
  41. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/transactional-outbox.md +0 -0
  42. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/core-features/workflows-activities.md +0 -0
  43. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/examples/ecommerce.md +0 -0
  44. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/examples/events.md +0 -0
  45. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/examples/fastapi-integration.md +0 -0
  46. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/examples/saga.md +0 -0
  47. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/examples/simple.md +0 -0
  48. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/getting-started/concepts.md +0 -0
  49. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/getting-started/first-workflow.md +0 -0
  50. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/getting-started/installation.md +0 -0
  51. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/getting-started/quick-start.md +0 -0
  52. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/index.md +0 -0
  53. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/integrations/mcp.md +0 -0
  54. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/integrations/mirascope.md +0 -0
  55. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/integrations/opentelemetry.md +0 -0
  56. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/integrations/pydantic-rpc.md +0 -0
  57. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/cloudevents-cli-trigger.png +0 -0
  58. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/compensation-execution.png +0 -0
  59. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/detail-page-loan-approval.png +0 -0
  60. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/detail-page-match-case.png +0 -0
  61. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/nested-pydantic-form.png +0 -0
  62. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/start-workflow-form-pydantic.png +0 -0
  63. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/wait-event-visualization.png +0 -0
  64. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/workflow-list-view.png +0 -0
  65. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/images/workflow-selection-dropdown.png +0 -0
  66. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/setup.md +0 -0
  67. {edda_framework-0.14.1 → edda_framework-0.15.0}/docs/viewer-ui/visualization.md +0 -0
  68. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/__init__.py +0 -0
  69. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/activity.py +0 -0
  70. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/app.py +0 -0
  71. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/channels.py +0 -0
  72. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/compensation.py +0 -0
  73. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/context.py +0 -0
  74. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/exceptions.py +0 -0
  75. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/hooks.py +0 -0
  76. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/__init__.py +0 -0
  77. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mcp/__init__.py +0 -0
  78. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mcp/decorators.py +0 -0
  79. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mcp/server.py +0 -0
  80. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mirascope/__init__.py +0 -0
  81. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mirascope/agent.py +0 -0
  82. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mirascope/call.py +0 -0
  83. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mirascope/decorator.py +0 -0
  84. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/mirascope/types.py +0 -0
  85. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/opentelemetry/__init__.py +0 -0
  86. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/integrations/opentelemetry/hooks.py +0 -0
  87. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/locking.py +0 -0
  88. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/migrations/mysql/20251217000000_initial_schema.sql +0 -0
  89. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/migrations/postgresql/20251217000000_initial_schema.sql +0 -0
  90. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/migrations/sqlite/20251217000000_initial_schema.sql +0 -0
  91. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/outbox/__init__.py +0 -0
  92. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/outbox/relayer.py +0 -0
  93. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/outbox/transactional.py +0 -0
  94. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/pydantic_utils.py +0 -0
  95. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/replay.py +0 -0
  96. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/retry.py +0 -0
  97. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/serialization/__init__.py +0 -0
  98. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/serialization/base.py +0 -0
  99. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/serialization/json.py +0 -0
  100. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/__init__.py +0 -0
  101. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/migrations.py +0 -0
  102. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/models.py +0 -0
  103. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/notify_base.py +0 -0
  104. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/pg_notify.py +0 -0
  105. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/protocol.py +0 -0
  106. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/storage/sqlalchemy_storage.py +0 -0
  107. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/viewer_ui/__init__.py +0 -0
  108. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/viewer_ui/app.py +0 -0
  109. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/viewer_ui/components.py +0 -0
  110. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/viewer_ui/data_service.py +0 -0
  111. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/viewer_ui/theme.py +0 -0
  112. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/visualizer/__init__.py +0 -0
  113. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/visualizer/ast_analyzer.py +0 -0
  114. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/visualizer/mermaid_generator.py +0 -0
  115. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/workflow.py +0 -0
  116. {edda_framework-0.14.1 → edda_framework-0.15.0}/edda/wsgi.py +0 -0
  117. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/__init__.py +0 -0
  118. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/cancellable_workflow.py +0 -0
  119. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/compensation_workflow.py +0 -0
  120. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/event_waiting_app.py +0 -0
  121. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/event_waiting_workflow.py +0 -0
  122. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/event_waiting_workflow_complete.py +0 -0
  123. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/long_running_loop.py +0 -0
  124. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mcp/README.md +0 -0
  125. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mcp/order_processing_mcp.py +0 -0
  126. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mcp/prompts_example.py +0 -0
  127. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mcp/remote_server_example.py +0 -0
  128. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mcp/simple_mcp_server.py +0 -0
  129. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/message_passing.py +0 -0
  130. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mirascope/__init__.py +0 -0
  131. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mirascope/durable_agent.py +0 -0
  132. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mirascope/multi_turn.py +0 -0
  133. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mirascope/simple_call.py +0 -0
  134. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/mirascope/with_tools.py +0 -0
  135. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/observability_with_logfire.py +0 -0
  136. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/observability_with_opentelemetry.py +0 -0
  137. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/pydantic_rpc_integration.py +0 -0
  138. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/pydantic_saga.py +0 -0
  139. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/retry_example.py +0 -0
  140. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/retry_with_compensation.py +0 -0
  141. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/simple_workflow.py +0 -0
  142. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/typeddict_example.py +0 -0
  143. {edda_framework-0.14.1 → edda_framework-0.15.0}/examples/with_outbox.py +0 -0
  144. {edda_framework-0.14.1 → edda_framework-0.15.0}/schema/.dbmate.yml +0 -0
  145. {edda_framework-0.14.1 → edda_framework-0.15.0}/schema/.git +0 -0
  146. {edda_framework-0.14.1 → edda_framework-0.15.0}/schema/.gitignore +0 -0
  147. {edda_framework-0.14.1 → edda_framework-0.15.0}/schema/LICENSE +0 -0
  148. {edda_framework-0.14.1 → edda_framework-0.15.0}/schema/README.md +0 -0
  149. {edda_framework-0.14.1 → edda_framework-0.15.0}/schema/docs/column-values.md +0 -0
  150. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/__init__.py +0 -0
  151. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/conftest.py +0 -0
  152. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/__init__.py +0 -0
  153. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mcp/__init__.py +0 -0
  154. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mcp/test_cancel.py +0 -0
  155. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mcp/test_integration.py +0 -0
  156. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mcp/test_jsonrpc.py +0 -0
  157. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mcp/test_prompts.py +0 -0
  158. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mcp/test_server.py +0 -0
  159. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mirascope/__init__.py +0 -0
  160. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mirascope/test_agent.py +0 -0
  161. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mirascope/test_call.py +0 -0
  162. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mirascope/test_decorator.py +0 -0
  163. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/mirascope/test_types.py +0 -0
  164. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/opentelemetry/__init__.py +0 -0
  165. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/integrations/opentelemetry/test_hooks.py +0 -0
  166. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_activity.py +0 -0
  167. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_activity_retry.py +0 -0
  168. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_activity_sync.py +0 -0
  169. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_app.py +0 -0
  170. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_ast_analyzer.py +0 -0
  171. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_atomic_wait_event.py +0 -0
  172. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_auto_migration.py +0 -0
  173. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_binary_data.py +0 -0
  174. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_channel_competing.py +0 -0
  175. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_channel_direct.py +0 -0
  176. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_channel_mode_locking.py +0 -0
  177. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_channel_transactional.py +0 -0
  178. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_cloudevents_http_binding.py +0 -0
  179. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_compensation.py +0 -0
  180. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_compensation_crash_recovery.py.wip +0 -0
  181. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_concurrent_outbox.py +0 -0
  182. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_context.py +0 -0
  183. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_cross_language_channel.py +0 -0
  184. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_ctx_session.py +0 -0
  185. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_distributed_event_delivery.py +0 -0
  186. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_events.py +0 -0
  187. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_instance_id_routing.py +0 -0
  188. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_lock_race_condition.py +0 -0
  189. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_lock_timeout_customization.py +0 -0
  190. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_locking.py +0 -0
  191. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_message_cleanup.py +0 -0
  192. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_message_delivery_lock.py +0 -0
  193. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_messages.py +0 -0
  194. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_migrations_integration.py +0 -0
  195. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_multidb_storage.py +0 -0
  196. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_outbox.py +0 -0
  197. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_pg_notify.py +0 -0
  198. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_polling_optimization.py +0 -0
  199. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_pydantic_activity.py +0 -0
  200. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_pydantic_enum.py +0 -0
  201. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_pydantic_events.py +0 -0
  202. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_pydantic_saga.py +0 -0
  203. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_pydantic_utils.py +0 -0
  204. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_receive_timeout.py +0 -0
  205. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_received_event.py +0 -0
  206. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_recur.py +0 -0
  207. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_recur_cleanup.py +0 -0
  208. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_replay.py +0 -0
  209. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_retry_policy.py +0 -0
  210. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_saga_parameter_extraction.py +0 -0
  211. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_serialization.py +0 -0
  212. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_skip_locked.py +0 -0
  213. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_stale_workflow_recovery.py +0 -0
  214. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_storage.py +0 -0
  215. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_storage_mysql.py +0 -0
  216. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_storage_postgresql.py +0 -0
  217. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_transactions.py +0 -0
  218. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_viewer_pagination.py +0 -0
  219. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_viewer_pydantic_form.py +0 -0
  220. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_viewer_start_saga.py +0 -0
  221. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_wait_timer.py +0 -0
  222. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_workflow.py +0 -0
  223. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_workflow_auto_register.py +0 -0
  224. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_workflow_cancellation.py +0 -0
  225. {edda_framework-0.14.1 → edda_framework-0.15.0}/tests/test_workflow_resumption.py +0 -0
  226. {edda_framework-0.14.1 → edda_framework-0.15.0}/viewer_app.py +0 -0
  227. {edda_framework-0.14.1 → 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.1
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
@@ -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
+ ]
@@ -0,0 +1,81 @@
1
+ """DurableGraphContext - bridges pydantic-graph and Edda contexts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING, Generic, TypeVar
7
+
8
+ if TYPE_CHECKING:
9
+ from edda.context import WorkflowContext
10
+
11
+ from .nodes import ReceivedEvent
12
+
13
+ StateT = TypeVar("StateT")
14
+ DepsT = TypeVar("DepsT")
15
+
16
+
17
+ @dataclass
18
+ class DurableGraphContext(Generic[StateT, DepsT]):
19
+ """
20
+ Context that bridges pydantic-graph and Edda.
21
+
22
+ Provides access to:
23
+ - pydantic-graph's state and deps via properties
24
+ - last_event: The most recent event received via WaitForEvent
25
+
26
+ This context is passed to node's run() method when executing
27
+ via DurableGraph.
28
+
29
+ For durable wait operations (wait_event, sleep), use the WaitForEvent
30
+ and Sleep marker nodes instead of calling methods directly:
31
+
32
+ from edda.integrations.graph import WaitForEvent, Sleep
33
+
34
+ @dataclass
35
+ class MyNode(BaseNode[MyState, None, str]):
36
+ async def run(self, ctx: DurableGraphContext) -> WaitForEvent[NextNode]:
37
+ # Return a marker to wait for an event
38
+ return WaitForEvent(
39
+ event_type="payment.completed",
40
+ next_node=NextNode(),
41
+ timeout_seconds=3600,
42
+ )
43
+
44
+ @dataclass
45
+ class NextNode(BaseNode[MyState, None, str]):
46
+ async def run(self, ctx: DurableGraphContext) -> End[str]:
47
+ # Access the received event
48
+ event = ctx.last_event
49
+ return End(event.data.get("status", "unknown"))
50
+
51
+ Attributes:
52
+ state: The graph state object (mutable, shared across nodes)
53
+ deps: The dependencies object (immutable)
54
+ last_event: The most recent event received via WaitForEvent (or None)
55
+ workflow_ctx: The Edda WorkflowContext
56
+ """
57
+
58
+ _state: StateT
59
+ _deps: DepsT
60
+ workflow_ctx: WorkflowContext
61
+ last_event: ReceivedEvent | None = field(default=None)
62
+
63
+ @property
64
+ def state(self) -> StateT:
65
+ """Get the graph state object."""
66
+ return self._state
67
+
68
+ @property
69
+ def deps(self) -> DepsT:
70
+ """Get the dependencies object."""
71
+ return self._deps
72
+
73
+ @property
74
+ def instance_id(self) -> str:
75
+ """Get the workflow instance ID."""
76
+ return self.workflow_ctx.instance_id
77
+
78
+ @property
79
+ def is_replaying(self) -> bool:
80
+ """Check if the workflow is currently replaying."""
81
+ return self.workflow_ctx.is_replaying
@@ -0,0 +1,9 @@
1
+ """Exceptions for durable graph integration."""
2
+
3
+
4
+ class GraphExecutionError(Exception):
5
+ """Raised when a graph node execution fails."""
6
+
7
+ def __init__(self, message: str, node_name: str | None = None) -> None:
8
+ self.node_name = node_name
9
+ super().__init__(message)