edda-framework 0.9.1__tar.gz → 0.11.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 (193) hide show
  1. {edda_framework-0.9.1 → edda_framework-0.11.0}/Justfile +19 -2
  2. {edda_framework-0.9.1 → edda_framework-0.11.0}/PKG-INFO +17 -1
  3. {edda_framework-0.9.1 → edda_framework-0.11.0}/README.md +9 -0
  4. {edda_framework-0.9.1 → edda_framework-0.11.0}/demo_app.py +24 -6
  5. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/api/reference.md +8 -0
  6. edda_framework-0.11.0/docs/core-features/events/postgres-notify.md +166 -0
  7. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/getting-started/installation.md +45 -0
  8. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/index.md +1 -0
  9. edda_framework-0.11.0/docs/integrations/mirascope.md +373 -0
  10. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/setup.md +13 -1
  11. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/app.py +419 -26
  12. edda_framework-0.11.0/edda/integrations/mirascope/__init__.py +78 -0
  13. edda_framework-0.11.0/edda/integrations/mirascope/agent.py +467 -0
  14. edda_framework-0.11.0/edda/integrations/mirascope/call.py +166 -0
  15. edda_framework-0.11.0/edda/integrations/mirascope/decorator.py +163 -0
  16. edda_framework-0.11.0/edda/integrations/mirascope/types.py +268 -0
  17. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/outbox/relayer.py +21 -2
  18. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/storage/__init__.py +8 -0
  19. edda_framework-0.11.0/edda/storage/notify_base.py +162 -0
  20. edda_framework-0.11.0/edda/storage/pg_notify.py +325 -0
  21. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/storage/protocol.py +9 -1
  22. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/storage/sqlalchemy_storage.py +193 -13
  23. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/viewer_ui/app.py +26 -0
  24. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/viewer_ui/data_service.py +4 -0
  25. edda_framework-0.11.0/examples/mirascope/__init__.py +6 -0
  26. edda_framework-0.11.0/examples/mirascope/durable_agent.py +421 -0
  27. edda_framework-0.11.0/examples/mirascope/multi_turn.py +150 -0
  28. edda_framework-0.11.0/examples/mirascope/simple_call.py +167 -0
  29. edda_framework-0.11.0/examples/mirascope/with_tools.py +198 -0
  30. {edda_framework-0.9.1 → edda_framework-0.11.0}/pyproject.toml +19 -1
  31. edda_framework-0.11.0/tests/integrations/mirascope/__init__.py +1 -0
  32. edda_framework-0.11.0/tests/integrations/mirascope/test_agent.py +403 -0
  33. edda_framework-0.11.0/tests/integrations/mirascope/test_call.py +183 -0
  34. edda_framework-0.11.0/tests/integrations/mirascope/test_decorator.py +235 -0
  35. edda_framework-0.11.0/tests/integrations/mirascope/test_types.py +449 -0
  36. edda_framework-0.11.0/tests/test_pg_notify.py +298 -0
  37. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_storage.py +156 -0
  38. {edda_framework-0.9.1 → edda_framework-0.11.0}/uv.lock +399 -33
  39. {edda_framework-0.9.1 → edda_framework-0.11.0}/zensical.toml +1 -0
  40. {edda_framework-0.9.1 → edda_framework-0.11.0}/.github/workflows/ci.yml +0 -0
  41. {edda_framework-0.9.1 → edda_framework-0.11.0}/.github/workflows/docs.yml +0 -0
  42. {edda_framework-0.9.1 → edda_framework-0.11.0}/.github/workflows/release.yml +0 -0
  43. {edda_framework-0.9.1 → edda_framework-0.11.0}/.gitignore +0 -0
  44. {edda_framework-0.9.1 → edda_framework-0.11.0}/.python-version +0 -0
  45. {edda_framework-0.9.1 → edda_framework-0.11.0}/LICENSE +0 -0
  46. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/durable-execution/replay.md +0 -0
  47. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/events/cloudevents-http-binding.md +0 -0
  48. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/events/wait-event.md +0 -0
  49. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/hooks.md +0 -0
  50. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/messages.md +0 -0
  51. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/retry.md +0 -0
  52. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/saga-compensation.md +0 -0
  53. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/transactional-outbox.md +0 -0
  54. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/core-features/workflows-activities.md +0 -0
  55. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/examples/ecommerce.md +0 -0
  56. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/examples/events.md +0 -0
  57. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/examples/fastapi-integration.md +0 -0
  58. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/examples/saga.md +0 -0
  59. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/examples/simple.md +0 -0
  60. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/getting-started/concepts.md +0 -0
  61. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/getting-started/first-workflow.md +0 -0
  62. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/getting-started/quick-start.md +0 -0
  63. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/integrations/mcp.md +0 -0
  64. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/integrations/opentelemetry.md +0 -0
  65. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/integrations/pydantic-rpc.md +0 -0
  66. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/cloudevents-cli-trigger.png +0 -0
  67. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/compensation-execution.png +0 -0
  68. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/detail-page-loan-approval.png +0 -0
  69. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/detail-page-match-case.png +0 -0
  70. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/nested-pydantic-form.png +0 -0
  71. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/start-workflow-form-pydantic.png +0 -0
  72. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/wait-event-visualization.png +0 -0
  73. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/workflow-list-view.png +0 -0
  74. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/images/workflow-selection-dropdown.png +0 -0
  75. {edda_framework-0.9.1 → edda_framework-0.11.0}/docs/viewer-ui/visualization.md +0 -0
  76. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/__init__.py +0 -0
  77. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/activity.py +0 -0
  78. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/channels.py +0 -0
  79. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/compensation.py +0 -0
  80. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/context.py +0 -0
  81. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/exceptions.py +0 -0
  82. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/hooks.py +0 -0
  83. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/integrations/__init__.py +0 -0
  84. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/integrations/mcp/__init__.py +0 -0
  85. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/integrations/mcp/decorators.py +0 -0
  86. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/integrations/mcp/server.py +0 -0
  87. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/integrations/opentelemetry/__init__.py +0 -0
  88. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/integrations/opentelemetry/hooks.py +0 -0
  89. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/locking.py +0 -0
  90. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/outbox/__init__.py +0 -0
  91. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/outbox/transactional.py +0 -0
  92. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/pydantic_utils.py +0 -0
  93. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/replay.py +0 -0
  94. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/retry.py +0 -0
  95. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/serialization/__init__.py +0 -0
  96. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/serialization/base.py +0 -0
  97. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/serialization/json.py +0 -0
  98. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/storage/models.py +0 -0
  99. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/viewer_ui/__init__.py +0 -0
  100. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/viewer_ui/components.py +0 -0
  101. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/viewer_ui/theme.py +0 -0
  102. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/visualizer/__init__.py +0 -0
  103. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/visualizer/ast_analyzer.py +0 -0
  104. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/visualizer/mermaid_generator.py +0 -0
  105. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/workflow.py +0 -0
  106. {edda_framework-0.9.1 → edda_framework-0.11.0}/edda/wsgi.py +0 -0
  107. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/__init__.py +0 -0
  108. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/cancellable_workflow.py +0 -0
  109. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/compensation_workflow.py +0 -0
  110. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/event_waiting_app.py +0 -0
  111. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/event_waiting_workflow.py +0 -0
  112. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/event_waiting_workflow_complete.py +0 -0
  113. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/long_running_loop.py +0 -0
  114. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/mcp/README.md +0 -0
  115. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/mcp/order_processing_mcp.py +0 -0
  116. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/mcp/prompts_example.py +0 -0
  117. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/mcp/remote_server_example.py +0 -0
  118. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/mcp/simple_mcp_server.py +0 -0
  119. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/message_passing.py +0 -0
  120. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/observability_with_logfire.py +0 -0
  121. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/observability_with_opentelemetry.py +0 -0
  122. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/pydantic_rpc_integration.py +0 -0
  123. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/pydantic_saga.py +0 -0
  124. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/retry_example.py +0 -0
  125. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/retry_with_compensation.py +0 -0
  126. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/simple_workflow.py +0 -0
  127. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/typeddict_example.py +0 -0
  128. {edda_framework-0.9.1 → edda_framework-0.11.0}/examples/with_outbox.py +0 -0
  129. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/__init__.py +0 -0
  130. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/conftest.py +0 -0
  131. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/__init__.py +0 -0
  132. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/mcp/__init__.py +0 -0
  133. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/mcp/test_cancel.py +0 -0
  134. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/mcp/test_integration.py +0 -0
  135. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/mcp/test_jsonrpc.py +0 -0
  136. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/mcp/test_prompts.py +0 -0
  137. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/mcp/test_server.py +0 -0
  138. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/opentelemetry/__init__.py +0 -0
  139. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/integrations/opentelemetry/test_hooks.py +0 -0
  140. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_activity.py +0 -0
  141. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_activity_retry.py +0 -0
  142. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_activity_sync.py +0 -0
  143. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_app.py +0 -0
  144. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_ast_analyzer.py +0 -0
  145. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_atomic_wait_event.py +0 -0
  146. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_auto_migration.py +0 -0
  147. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_binary_data.py +0 -0
  148. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_channel_competing.py +0 -0
  149. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_channel_transactional.py +0 -0
  150. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_cloudevents_http_binding.py +0 -0
  151. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_compensation.py +0 -0
  152. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_compensation_crash_recovery.py.wip +0 -0
  153. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_concurrent_outbox.py +0 -0
  154. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_context.py +0 -0
  155. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_ctx_session.py +0 -0
  156. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_distributed_event_delivery.py +0 -0
  157. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_events.py +0 -0
  158. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_instance_id_routing.py +0 -0
  159. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_lock_race_condition.py +0 -0
  160. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_lock_timeout_customization.py +0 -0
  161. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_locking.py +0 -0
  162. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_message_cleanup.py +0 -0
  163. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_message_delivery_lock.py +0 -0
  164. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_messages.py +0 -0
  165. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_multidb_storage.py +0 -0
  166. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_outbox.py +0 -0
  167. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_pydantic_activity.py +0 -0
  168. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_pydantic_enum.py +0 -0
  169. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_pydantic_events.py +0 -0
  170. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_pydantic_saga.py +0 -0
  171. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_pydantic_utils.py +0 -0
  172. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_receive_timeout.py +0 -0
  173. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_received_event.py +0 -0
  174. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_recur.py +0 -0
  175. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_recur_cleanup.py +0 -0
  176. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_replay.py +0 -0
  177. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_retry_policy.py +0 -0
  178. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_saga_parameter_extraction.py +0 -0
  179. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_serialization.py +0 -0
  180. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_skip_locked.py +0 -0
  181. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_stale_workflow_recovery.py +0 -0
  182. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_storage_mysql.py +0 -0
  183. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_storage_postgresql.py +0 -0
  184. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_transactions.py +0 -0
  185. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_viewer_pagination.py +0 -0
  186. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_viewer_pydantic_form.py +0 -0
  187. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_viewer_start_saga.py +0 -0
  188. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_wait_timer.py +0 -0
  189. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_workflow.py +0 -0
  190. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_workflow_auto_register.py +0 -0
  191. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_workflow_cancellation.py +0 -0
  192. {edda_framework-0.9.1 → edda_framework-0.11.0}/tests/test_workflow_resumption.py +0 -0
  193. {edda_framework-0.9.1 → edda_framework-0.11.0}/viewer_app.py +0 -0
@@ -11,7 +11,7 @@ install:
11
11
 
12
12
  # Install all dependencies including database drivers
13
13
  install-all:
14
- uv sync --extra dev --extra postgresql --extra mysql
14
+ uv sync --extra dev --extra postgresql --extra mysql --extra postgres-notify
15
15
 
16
16
  # Install viewer dependencies
17
17
  install-viewer:
@@ -71,15 +71,32 @@ demo:
71
71
  # Run demo application with PostgreSQL (requires local PostgreSQL)
72
72
  # Usage: just demo-postgresql
73
73
  # Requires: EDDA_POSTGRES_PASSWORD environment variable
74
+ # Uses LISTEN/NOTIFY for instant notifications (auto-detected)
74
75
  demo-postgresql:
76
+ @echo "Stopping existing demo app on port 8001..."
77
+ @lsof -ti :8001 | xargs kill -15 2>/dev/null || true
78
+ @sleep 1
79
+ @lsof -ti :8001 | xargs kill -9 2>/dev/null || true
80
+ @echo "Ensuring server and PostgreSQL dependencies are installed..."
81
+ @uv sync --extra server --extra postgresql --extra postgres-notify --quiet
82
+ @echo "Starting demo app with PostgreSQL (NOTIFY enabled)..."
83
+ EDDA_DB_URL="postgresql+asyncpg://postgres:{{env_var('EDDA_POSTGRES_PASSWORD')}}@localhost:5432/edda" \
84
+ uv run tsuno demo_app:application --bind 127.0.0.1:8001
85
+
86
+ # Run demo application with PostgreSQL but WITHOUT NOTIFY (polling only)
87
+ # Usage: just demo-postgresql-polling
88
+ # Requires: EDDA_POSTGRES_PASSWORD environment variable
89
+ # Uses polling-only mode (no LISTEN/NOTIFY) for comparison testing
90
+ demo-postgresql-polling:
75
91
  @echo "Stopping existing demo app on port 8001..."
76
92
  @lsof -ti :8001 | xargs kill -15 2>/dev/null || true
77
93
  @sleep 1
78
94
  @lsof -ti :8001 | xargs kill -9 2>/dev/null || true
79
95
  @echo "Ensuring server and PostgreSQL dependencies are installed..."
80
96
  @uv sync --extra server --extra postgresql --quiet
81
- @echo "Starting demo app with PostgreSQL..."
97
+ @echo "Starting demo app with PostgreSQL (polling only, NOTIFY disabled)..."
82
98
  EDDA_DB_URL="postgresql+asyncpg://postgres:{{env_var('EDDA_POSTGRES_PASSWORD')}}@localhost:5432/edda" \
99
+ EDDA_USE_NOTIFY=false \
83
100
  uv run tsuno demo_app:application --bind 127.0.0.1:8001
84
101
 
85
102
  # Run viewer with demo_app using PostgreSQL (requires local PostgreSQL)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.9.1
3
+ Version: 0.11.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
@@ -28,6 +28,8 @@ Requires-Dist: httpx>=0.28.1
28
28
  Requires-Dist: pydantic>=2.0.0
29
29
  Requires-Dist: sqlalchemy[asyncio]>=2.0.0
30
30
  Requires-Dist: uvloop>=0.22.1
31
+ Provides-Extra: cpu-monitor
32
+ Requires-Dist: psutil>=5.9.0; extra == 'cpu-monitor'
31
33
  Provides-Extra: dev
32
34
  Requires-Dist: black>=25.9.0; extra == 'dev'
33
35
  Requires-Dist: mcp>=1.22.0; extra == 'dev'
@@ -42,12 +44,17 @@ Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
42
44
  Requires-Dist: tsuno>=0.1.3; extra == 'dev'
43
45
  Provides-Extra: mcp
44
46
  Requires-Dist: mcp>=1.22.0; extra == 'mcp'
47
+ Provides-Extra: mirascope
48
+ Requires-Dist: mirascope[anthropic,google,openai]>=2.0.0a0; extra == 'mirascope'
49
+ Requires-Dist: pydantic-settings>=2.0.0; extra == 'mirascope'
45
50
  Provides-Extra: mysql
46
51
  Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
47
52
  Provides-Extra: opentelemetry
48
53
  Requires-Dist: opentelemetry-api>=1.20.0; extra == 'opentelemetry'
49
54
  Requires-Dist: opentelemetry-exporter-otlp>=1.20.0; extra == 'opentelemetry'
50
55
  Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'opentelemetry'
56
+ Provides-Extra: postgres-notify
57
+ Requires-Dist: asyncpg>=0.30.0; extra == 'postgres-notify'
51
58
  Provides-Extra: postgresql
52
59
  Requires-Dist: asyncpg>=0.30.0; extra == 'postgresql'
53
60
  Provides-Extra: server
@@ -87,7 +94,9 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
87
94
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
88
95
  - ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
89
96
  - 📬 **Channel-based Messaging**: Actor-model style communication with competing (job queue) and broadcast (fan-out) modes
97
+ - ⚡ **Instant Notifications**: PostgreSQL LISTEN/NOTIFY for near-instant event delivery (optional)
90
98
  - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
99
+ - 🧠 **Mirascope Integration**: Durable LLM calls
91
100
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
92
101
 
93
102
  ## Use Cases
@@ -221,6 +230,9 @@ uv add edda-framework --extra mysql
221
230
  # With Viewer UI
222
231
  uv add edda-framework --extra viewer
223
232
 
233
+ # With PostgreSQL instant notifications (LISTEN/NOTIFY)
234
+ uv add edda-framework --extra postgres-notify
235
+
224
236
  # All extras (PostgreSQL, MySQL, Viewer UI)
225
237
  uv add edda-framework --extra postgresql --extra mysql --extra viewer
226
238
  ```
@@ -272,6 +284,8 @@ pip install "git+https://github.com/i2y/edda.git[postgresql,viewer]"
272
284
 
273
285
  **Important**: For multi-process or multi-pod deployments (K8s, Docker Compose with multiple replicas, etc.), you **must** use PostgreSQL or MySQL. SQLite supports multiple async workers within a single process, but its table-level locking makes it unsuitable for multi-process/multi-pod scenarios.
274
286
 
287
+ > **Tip**: For PostgreSQL, install the `postgres-notify` extra for near-instant event delivery using LISTEN/NOTIFY instead of polling.
288
+
275
289
  ### Development Installation
276
290
 
277
291
  If you want to contribute to Edda or modify the framework itself:
@@ -492,6 +506,8 @@ app = EddaApp(
492
506
  # Connection pool settings (optional)
493
507
  pool_size=5, # Concurrent connections
494
508
  max_overflow=10, # Additional burst capacity
509
+ # Batch processing (optional)
510
+ max_workflows_per_batch=10, # Or "auto" / "auto:cpu" for dynamic scaling
495
511
  )
496
512
  ```
497
513
 
@@ -29,7 +29,9 @@ For detailed documentation, visit [https://i2y.github.io/edda/](https://i2y.gith
29
29
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
30
30
  - ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
31
31
  - 📬 **Channel-based Messaging**: Actor-model style communication with competing (job queue) and broadcast (fan-out) modes
32
+ - ⚡ **Instant Notifications**: PostgreSQL LISTEN/NOTIFY for near-instant event delivery (optional)
32
33
  - 🤖 **MCP Integration**: Expose durable workflows as AI tools via Model Context Protocol
34
+ - 🧠 **Mirascope Integration**: Durable LLM calls
33
35
  - 🌍 **ASGI/WSGI Support**: Deploy with your preferred server (uvicorn, gunicorn, uWSGI)
34
36
 
35
37
  ## Use Cases
@@ -163,6 +165,9 @@ uv add edda-framework --extra mysql
163
165
  # With Viewer UI
164
166
  uv add edda-framework --extra viewer
165
167
 
168
+ # With PostgreSQL instant notifications (LISTEN/NOTIFY)
169
+ uv add edda-framework --extra postgres-notify
170
+
166
171
  # All extras (PostgreSQL, MySQL, Viewer UI)
167
172
  uv add edda-framework --extra postgresql --extra mysql --extra viewer
168
173
  ```
@@ -214,6 +219,8 @@ pip install "git+https://github.com/i2y/edda.git[postgresql,viewer]"
214
219
 
215
220
  **Important**: For multi-process or multi-pod deployments (K8s, Docker Compose with multiple replicas, etc.), you **must** use PostgreSQL or MySQL. SQLite supports multiple async workers within a single process, but its table-level locking makes it unsuitable for multi-process/multi-pod scenarios.
216
221
 
222
+ > **Tip**: For PostgreSQL, install the `postgres-notify` extra for near-instant event delivery using LISTEN/NOTIFY instead of polling.
223
+
217
224
  ### Development Installation
218
225
 
219
226
  If you want to contribute to Edda or modify the framework itself:
@@ -434,6 +441,8 @@ app = EddaApp(
434
441
  # Connection pool settings (optional)
435
442
  pool_size=5, # Concurrent connections
436
443
  max_overflow=10, # Additional burst capacity
444
+ # Batch processing (optional)
445
+ max_workflows_per_batch=10, # Or "auto" / "auto:cpu" for dynamic scaling
437
446
  )
438
447
  ```
439
448
 
@@ -1,6 +1,7 @@
1
1
  """Demo Edda ASGI application for tsuno."""
2
2
 
3
3
  import asyncio
4
+ import logging
4
5
  import os
5
6
  import sys
6
7
  from datetime import datetime
@@ -8,9 +9,17 @@ from enum import Enum
8
9
  from typing import Any, cast
9
10
 
10
11
  import uvloop
11
- from pydantic import BaseModel, Field, field_validator
12
12
 
13
- from edda import (
13
+ # Configure logging to see Edda startup messages (must be before edda import)
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
17
+ datefmt="%Y-%m-%d %H:%M:%S",
18
+ )
19
+
20
+ from pydantic import BaseModel, Field, field_validator # noqa: E402
21
+
22
+ from edda import ( # noqa: E402
14
23
  EddaApp,
15
24
  RetryPolicy,
16
25
  WorkflowContext,
@@ -24,7 +33,7 @@ from edda import (
24
33
  wait_until,
25
34
  workflow,
26
35
  )
27
- from edda.wsgi import create_wsgi_app
36
+ from edda.wsgi import create_wsgi_app # noqa: E402
28
37
 
29
38
  # Python 3.12+ uses asyncio.set_event_loop_policy() instead of uvloop.install()
30
39
  if sys.version_info >= (3, 12):
@@ -408,9 +417,18 @@ class ScheduledShipmentResult(BaseModel):
408
417
 
409
418
  # Create Edda application
410
419
  # Use environment variable EDDA_DB_URL if available, otherwise default to sqlite:///demo.db
420
+ # Use EDDA_USE_NOTIFY to control LISTEN/NOTIFY (true/false/auto, default: auto)
421
+ _use_notify_env = os.getenv("EDDA_USE_NOTIFY", "auto").lower()
422
+ _use_listen_notify: bool | None = None # Auto-detect
423
+ if _use_notify_env == "true":
424
+ _use_listen_notify = True
425
+ elif _use_notify_env == "false":
426
+ _use_listen_notify = False
427
+
411
428
  app = EddaApp(
412
429
  service_name="demo-service",
413
430
  db_url=os.getenv("EDDA_DB_URL", "sqlite:///demo.db"),
431
+ use_listen_notify=_use_listen_notify,
414
432
  )
415
433
 
416
434
 
@@ -2115,7 +2133,7 @@ async def job_publisher_workflow(
2115
2133
  """
2116
2134
  print(f"\n[PUBLISHER] Publishing job: {input.task}")
2117
2135
  await publish(ctx, "jobs", {"task": input.task})
2118
- print(f"[PUBLISHER] Job published to 'jobs' channel")
2136
+ print("[PUBLISHER] Job published to 'jobs' channel")
2119
2137
  return {"published": True, "channel": "jobs", "task": input.task}
2120
2138
 
2121
2139
 
@@ -2135,7 +2153,7 @@ async def notification_publisher_workflow(
2135
2153
  """
2136
2154
  print(f"\n[PUBLISHER] Publishing notification: {input.message}")
2137
2155
  await publish(ctx, "notifications", {"message": input.message})
2138
- print(f"[PUBLISHER] Notification published to 'notifications' channel")
2156
+ print("[PUBLISHER] Notification published to 'notifications' channel")
2139
2157
  return {"published": True, "channel": "notifications", "message": input.message}
2140
2158
 
2141
2159
 
@@ -2194,7 +2212,7 @@ async def direct_message_sender_workflow(
2194
2212
 
2195
2213
  await send_to(ctx, input.target_instance_id, {"message": input.message})
2196
2214
 
2197
- print(f"[SENDER] Message sent!")
2215
+ print("[SENDER] Message sent!")
2198
2216
  return DirectMessageSenderResult(
2199
2217
  sent=True,
2200
2218
  target_instance_id=input.target_instance_id,
@@ -20,6 +20,14 @@
20
20
 
21
21
  ---
22
22
 
23
+ ## Instance Search
24
+
25
+ ### find_instances
26
+
27
+ ::: edda.EddaApp.find_instances
28
+
29
+ ---
30
+
23
31
  ## Timer Functions
24
32
 
25
33
  ### sleep
@@ -0,0 +1,166 @@
1
+ # PostgreSQL LISTEN/NOTIFY
2
+
3
+ Edda supports PostgreSQL's LISTEN/NOTIFY mechanism for near-instant event and message delivery. This optional feature significantly reduces latency compared to polling-based delivery.
4
+
5
+ ## Overview
6
+
7
+ By default, Edda uses polling to check for new events and messages. With PostgreSQL LISTEN/NOTIFY enabled:
8
+
9
+ - **Event delivery**: Near-instant (milliseconds) instead of polling interval
10
+ - **Message delivery**: Near-instant instead of polling interval
11
+ - **Database load**: Reduced polling queries
12
+ - **Fallback**: Automatic fallback to polling if notifications are missed
13
+
14
+ ## Installation
15
+
16
+ Install the `postgres-notify` extra:
17
+
18
+ ```bash
19
+ # Using uv
20
+ uv add edda-framework --extra postgres-notify
21
+
22
+ # Using pip
23
+ pip install "edda-framework[postgres-notify]"
24
+ ```
25
+
26
+ This installs `asyncpg`, which is required for LISTEN/NOTIFY support.
27
+
28
+ ## Configuration
29
+
30
+ ### Basic Usage (Auto-detection)
31
+
32
+ ```python
33
+ from edda import EddaApp
34
+
35
+ # LISTEN/NOTIFY is auto-detected for PostgreSQL
36
+ app = EddaApp(
37
+ service_name="demo-service",
38
+ db_url="postgresql://user:password@localhost/workflows",
39
+ )
40
+ ```
41
+
42
+ When using PostgreSQL, LISTEN/NOTIFY is automatically enabled if `asyncpg` is installed.
43
+
44
+ ### Explicit Configuration
45
+
46
+ ```python
47
+ from edda import EddaApp
48
+
49
+ app = EddaApp(
50
+ service_name="demo-service",
51
+ db_url="postgresql://user:password@localhost/workflows",
52
+ use_listen_notify=True, # Force enable
53
+ notify_fallback_interval=30, # Fallback polling every 30 seconds
54
+ )
55
+ ```
56
+
57
+ ### Configuration Options
58
+
59
+ | Parameter | Type | Default | Description |
60
+ |-----------|------|---------|-------------|
61
+ | `use_listen_notify` | `bool \| None` | `None` | Notification mode: `None` = auto-detect, `True` = force enable, `False` = force disable |
62
+ | `notify_fallback_interval` | `int` | `30` | Fallback polling interval in seconds when NOTIFY is enabled |
63
+ | `max_workflows_per_batch` | `int \| "auto" \| "auto:cpu"` | `10` | Workflows per resume cycle. `"auto"` scales by queue depth, `"auto:cpu"` by CPU usage |
64
+
65
+ ### Auto-detection Behavior
66
+
67
+ | Database | `use_listen_notify=None` | `use_listen_notify=True` | `use_listen_notify=False` |
68
+ |----------|--------------------------|--------------------------|---------------------------|
69
+ | PostgreSQL | Enabled (if asyncpg installed) | Enabled (error if asyncpg missing) | Disabled (polling only) |
70
+ | MySQL | Disabled | Error | Disabled |
71
+ | SQLite | Disabled | Error | Disabled |
72
+
73
+ ## How It Works
74
+
75
+ ### Architecture
76
+
77
+ ```
78
+ ┌─────────────────┐ NOTIFY ┌─────────────────┐
79
+ │ Worker Pod 1 │ <────────────── │ PostgreSQL │
80
+ │ (Edda App) │ │ Database │
81
+ └─────────────────┘ └─────────────────┘
82
+ │ │
83
+ │ LISTEN │
84
+ └───────────────────────────────────┘
85
+ ```
86
+
87
+ 1. **Dedicated Connection**: Edda maintains a separate asyncpg connection for LISTEN/NOTIFY
88
+ 2. **Channel Subscription**: Subscribes to notification channels on startup
89
+ 3. **Instant Wakeup**: When a notification arrives, waiting workflows are immediately resumed
90
+ 4. **Fallback Polling**: Periodic polling ensures no notifications are missed
91
+
92
+ ### Notification Channels
93
+
94
+ Edda uses the following PostgreSQL channels:
95
+
96
+ - `edda_workflow_resume`: Notifies when workflows should be resumed
97
+ - `edda_outbox_ready`: Notifies when outbox events are ready for relay
98
+
99
+ ## Performance Comparison
100
+
101
+ | Metric | Polling Only | With LISTEN/NOTIFY |
102
+ |--------|--------------|-------------------|
103
+ | Event delivery latency | 0.5-1s (polling interval) | ~10-50ms |
104
+ | Message delivery latency | 0.5-1s | ~10-50ms |
105
+ | Database queries per idle workflow | Every 1s | Every 30s (fallback only) |
106
+ | Connection overhead | 1 per pool | +1 dedicated LISTEN connection |
107
+
108
+ ## Reliability Features
109
+
110
+ ### Automatic Reconnection
111
+
112
+ The LISTEN connection automatically reconnects on failure with configurable retry settings.
113
+
114
+ ### Fallback Polling
115
+
116
+ Even with NOTIFY enabled, Edda maintains fallback polling:
117
+
118
+ - **Default interval**: 30 seconds (`notify_fallback_interval`)
119
+ - **Purpose**: Catch any missed notifications
120
+ - **Behavior**: Polling runs in parallel with NOTIFY
121
+
122
+ ## Troubleshooting
123
+
124
+ ### asyncpg Not Installed
125
+
126
+ If you see a warning about asyncpg not being installed:
127
+
128
+ ```
129
+ WARNING: asyncpg not installed, falling back to polling-only mode.
130
+ Install with: pip install edda-framework[postgres-notify]
131
+ ```
132
+
133
+ **Solution**: Install the postgres-notify extra.
134
+
135
+ ### Force Enable on Non-PostgreSQL
136
+
137
+ If you see an error about LISTEN/NOTIFY requiring PostgreSQL:
138
+
139
+ ```
140
+ ValueError: use_listen_notify=True requires PostgreSQL database.
141
+ ```
142
+
143
+ **Solution**: Use `use_listen_notify=None` (auto-detect) or `False` for non-PostgreSQL databases.
144
+
145
+ ### Connection Lost
146
+
147
+ If you see reconnection messages in logs:
148
+
149
+ ```
150
+ INFO: Connection lost, attempting reconnection...
151
+ ```
152
+
153
+ This is normal behavior. Edda automatically reconnects, and workflows continue with polling fallback during reconnection.
154
+
155
+ ## Best Practices
156
+
157
+ 1. **Use auto-detection**: Set `use_listen_notify=None` (default) for portability across different database backends
158
+ 2. **Install postgres-notify in production**: For best performance with PostgreSQL
159
+ 3. **Monitor reconnections**: Frequent reconnection warnings may indicate network issues
160
+ 4. **Adjust fallback interval**: Lower values provide more reliability but increase database load
161
+
162
+ ## Related Topics
163
+
164
+ - [Event Waiting](wait-event.md) - How workflows wait for events
165
+ - [Channel-based Messaging](../messages.md) - Workflow-to-workflow communication
166
+ - [Installation](../../getting-started/installation.md) - Database setup
@@ -38,6 +38,9 @@ uv add edda-framework --extra mysql
38
38
  # With Viewer UI (workflow visualization)
39
39
  uv add edda-framework --extra viewer
40
40
 
41
+ # With PostgreSQL instant notifications (LISTEN/NOTIFY)
42
+ uv add edda-framework --extra postgres-notify
43
+
41
44
  # All extras (PostgreSQL + MySQL + Viewer UI)
42
45
  uv add edda-framework --extra postgresql --extra mysql --extra viewer
43
46
  ```
@@ -48,6 +51,7 @@ uv add edda-framework --extra postgresql --extra mysql --extra viewer
48
51
  - **postgresql**: `asyncpg` driver for PostgreSQL
49
52
  - **mysql**: `aiomysql` driver for MySQL
50
53
  - **viewer**: `nicegui` and `httpx` for workflow visualization UI
54
+ - **postgres-notify**: `asyncpg` driver for PostgreSQL LISTEN/NOTIFY instant notifications
51
55
 
52
56
  ### Using pip
53
57
 
@@ -66,6 +70,9 @@ pip install "edda-framework[mysql]"
66
70
  # With Viewer UI
67
71
  pip install "edda-framework[viewer]"
68
72
 
73
+ # With PostgreSQL instant notifications
74
+ pip install "edda-framework[postgres-notify]"
75
+
69
76
  # All extras
70
77
  pip install "edda-framework[postgresql,mysql,viewer]"
71
78
  ```
@@ -244,6 +251,44 @@ app = EddaApp(
244
251
  )
245
252
  ```
246
253
 
254
+ #### Enabling Instant Notifications (LISTEN/NOTIFY)
255
+
256
+ For near-instant event and message delivery, enable PostgreSQL LISTEN/NOTIFY:
257
+
258
+ ```bash
259
+ # Install the postgres-notify extra
260
+ uv add edda-framework --extra postgres-notify
261
+ # or
262
+ pip install "edda-framework[postgres-notify]"
263
+ ```
264
+
265
+ ```python
266
+ from edda import EddaApp
267
+
268
+ app = EddaApp(
269
+ service_name="demo-service",
270
+ db_url="postgresql://user:password@localhost/edda_workflows",
271
+ use_listen_notify=True, # Enable LISTEN/NOTIFY (auto-detected by default)
272
+ notify_fallback_interval=30, # Fallback polling interval in seconds
273
+ )
274
+ ```
275
+
276
+ **Configuration options:**
277
+
278
+ | Parameter | Type | Default | Description |
279
+ |-----------|------|---------|-------------|
280
+ | `use_listen_notify` | `bool \| None` | `None` | `None` = auto-detect (enabled for PostgreSQL), `True` = force enable, `False` = force disable |
281
+ | `notify_fallback_interval` | `int` | `30` | Fallback polling interval in seconds when NOTIFY is enabled |
282
+
283
+ **Benefits:**
284
+
285
+ - Near-instant event delivery (milliseconds vs. seconds with polling)
286
+ - Reduced database load (fewer polling queries)
287
+ - Automatic fallback to polling if NOTIFY fails
288
+ - Automatic reconnection on connection loss
289
+
290
+ See [PostgreSQL LISTEN/NOTIFY](../core-features/events/postgres-notify.md) for detailed documentation.
291
+
247
292
  ### MySQL
248
293
 
249
294
  1. **Install MySQL** (if not already installed)
@@ -27,6 +27,7 @@ Edda is a lightweight durable execution framework for Python that runs as a **li
27
27
  - ☁️ **CloudEvents Support**: Native support for CloudEvents protocol
28
28
  - ⏱️ **Event & Timer Waiting**: Free up worker resources while waiting for events or timers, resume on any available worker
29
29
  - 📬 **Message Passing**: Channel-based messaging (broadcast/competing modes) and direct workflow-to-workflow communication
30
+ - ⚡ **Instant Notifications**: Optional PostgreSQL LISTEN/NOTIFY for near-instant event delivery
30
31
 
31
32
  ## Use Cases
32
33