pyworkflow-engine 0.2.2__tar.gz → 0.2.5__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 (229) hide show
  1. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/PKG-INFO +1 -1
  2. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyproject.toml +1 -1
  3. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/__init__.py +1 -1
  4. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/app.py +7 -23
  5. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/tasks.py +35 -80
  6. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/local.py +13 -0
  7. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/step.py +3 -2
  8. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/base.py +58 -0
  9. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/cassandra.py +320 -0
  10. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/citus.py +111 -0
  11. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/dynamodb.py +304 -0
  12. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/file.py +266 -0
  13. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/memory.py +57 -5
  14. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/migrations/base.py +20 -0
  15. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/mysql.py +341 -0
  16. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/postgres.py +483 -0
  17. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/sqlite.py +425 -0
  18. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/context.py +20 -2
  19. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/decorator.py +20 -3
  20. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/dispatcher.py +98 -13
  21. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/emit.py +18 -0
  22. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/signal.py +1 -0
  23. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_schema_migrations.py +1 -1
  24. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/CLAUDE.md +0 -0
  25. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/DISTRIBUTED.md +0 -0
  26. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/LICENSE +0 -0
  27. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/MANIFEST.in +0 -0
  28. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/README.md +0 -0
  29. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/RELEASING.md +0 -0
  30. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/architecture.md +0 -0
  31. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/cancellation.mdx +0 -0
  32. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/continue-as-new.mdx +0 -0
  33. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/events.mdx +0 -0
  34. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/fault-tolerance.mdx +0 -0
  35. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/hooks.mdx +0 -0
  36. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/limitations.mdx +0 -0
  37. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/schedules.mdx +0 -0
  38. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/sleep.mdx +0 -0
  39. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/step-context.mdx +0 -0
  40. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/steps.mdx +0 -0
  41. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/concepts/workflows.mdx +0 -0
  42. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/conventions.md +0 -0
  43. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/guides/brokers.mdx +0 -0
  44. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/guides/cli.mdx +0 -0
  45. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/guides/configuration.mdx +0 -0
  46. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/harness-gaps.md +0 -0
  47. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/introduction.mdx +0 -0
  48. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/layers.md +0 -0
  49. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/docs/quickstart.mdx +0 -0
  50. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/__init__.py +0 -0
  51. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/__init__.py +0 -0
  52. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/docker-compose.yml +0 -0
  53. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/pyworkflow.config.yaml +0 -0
  54. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/__init__.py +0 -0
  55. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/basic.py +0 -0
  56. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/batch_processing.py +0 -0
  57. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/cancellation.py +0 -0
  58. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/child_workflow_from_step.py +0 -0
  59. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/child_workflow_patterns.py +0 -0
  60. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/child_workflows.py +0 -0
  61. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/continue_as_new.py +0 -0
  62. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/fault_tolerance.py +0 -0
  63. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/hooks.py +0 -0
  64. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/idempotency.py +0 -0
  65. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/long_running.py +0 -0
  66. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/retries.py +0 -0
  67. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/schedules.py +0 -0
  68. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/sleep_in_step.py +0 -0
  69. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/durable/workflows/step_context.py +0 -0
  70. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/01_basic_workflow.py +0 -0
  71. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/02_fault_tolerance.py +0 -0
  72. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/__init__.py +0 -0
  73. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/celery/transient/pyworkflow.config.yaml +0 -0
  74. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/__init__.py +0 -0
  75. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/01_basic_workflow.py +0 -0
  76. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/02_file_storage.py +0 -0
  77. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/03_retries.py +0 -0
  78. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/04_long_running.py +0 -0
  79. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/05_event_log.py +0 -0
  80. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/06_idempotency.py +0 -0
  81. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/07_hooks.py +0 -0
  82. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/08_cancellation.py +0 -0
  83. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/09_child_workflows.py +0 -0
  84. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/10_child_workflow_patterns.py +0 -0
  85. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/11_continue_as_new.py +0 -0
  86. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/12_schedules.py +0 -0
  87. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/13_step_context.py +0 -0
  88. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/14_child_workflow_from_step.py +0 -0
  89. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/durable/__init__.py +0 -0
  90. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/01_quick_tasks.py +0 -0
  91. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/02_retries.py +0 -0
  92. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/03_sleep.py +0 -0
  93. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/examples/local/transient/__init__.py +0 -0
  94. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/__init__.py +0 -0
  95. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/context.py +0 -0
  96. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/handler.py +0 -0
  97. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/aws/testing.py +0 -0
  98. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/__init__.py +0 -0
  99. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/loop.py +0 -0
  100. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/scheduler.py +0 -0
  101. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/celery/singleton.py +0 -0
  102. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/__init__.py +0 -0
  103. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/__main__.py +0 -0
  104. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/__init__.py +0 -0
  105. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/hooks.py +0 -0
  106. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/quickstart.py +0 -0
  107. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/runs.py +0 -0
  108. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/scheduler.py +0 -0
  109. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/schedules.py +0 -0
  110. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/setup.py +0 -0
  111. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/worker.py +0 -0
  112. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/commands/workflows.py +0 -0
  113. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/output/__init__.py +0 -0
  114. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/output/formatters.py +0 -0
  115. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/output/styles.py +0 -0
  116. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/__init__.py +0 -0
  117. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/async_helpers.py +0 -0
  118. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/config.py +0 -0
  119. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/config_generator.py +0 -0
  120. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/discovery.py +0 -0
  121. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/docker_manager.py +0 -0
  122. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/interactive.py +0 -0
  123. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/cli/utils/storage.py +0 -0
  124. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/config.py +0 -0
  125. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/__init__.py +0 -0
  126. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/aws.py +0 -0
  127. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/base.py +0 -0
  128. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/mock.py +0 -0
  129. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/context/step_context.py +0 -0
  130. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/__init__.py +0 -0
  131. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/exceptions.py +0 -0
  132. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/registry.py +0 -0
  133. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/scheduled.py +0 -0
  134. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/validation.py +0 -0
  135. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/core/workflow.py +0 -0
  136. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/discovery.py +0 -0
  137. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/__init__.py +0 -0
  138. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/events.py +0 -0
  139. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/executor.py +0 -0
  140. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/engine/replay.py +0 -0
  141. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/observability/__init__.py +0 -0
  142. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/observability/logging.py +0 -0
  143. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/__init__.py +0 -0
  144. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/child_handle.py +0 -0
  145. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/child_workflow.py +0 -0
  146. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/continue_as_new.py +0 -0
  147. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/define_hook.py +0 -0
  148. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/hooks.py +0 -0
  149. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/resume_hook.py +0 -0
  150. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/schedule.py +0 -0
  151. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/shield.py +0 -0
  152. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/sleep.py +0 -0
  153. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/step_checkpoint.py +0 -0
  154. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/primitives/step_hook.py +0 -0
  155. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/__init__.py +0 -0
  156. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/base.py +0 -0
  157. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/celery.py +0 -0
  158. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/factory.py +0 -0
  159. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/runtime/local.py +0 -0
  160. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/scheduler/__init__.py +0 -0
  161. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/scheduler/local.py +0 -0
  162. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/serialization/__init__.py +0 -0
  163. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/serialization/decoder.py +0 -0
  164. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/serialization/encoder.py +0 -0
  165. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/__init__.py +0 -0
  166. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/config.py +0 -0
  167. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/migrations/__init__.py +0 -0
  168. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/storage/schemas.py +0 -0
  169. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/__init__.py +0 -0
  170. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/checkpoint.py +0 -0
  171. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/consumer.py +0 -0
  172. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/registry.py +0 -0
  173. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/streams/step_context.py +0 -0
  174. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/__init__.py +0 -0
  175. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/duration.py +0 -0
  176. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/helpers.py +0 -0
  177. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow/utils/schedule.py +0 -0
  178. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/pyworkflow_engine.egg-info/SOURCES.txt +0 -0
  179. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/setup.cfg +0 -0
  180. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/__init__.py +0 -0
  181. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_cancellation.py +0 -0
  182. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_cassandra_storage.py +0 -0
  183. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_child_workflows.py +0 -0
  184. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_continue_as_new.py +0 -0
  185. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_dynamodb_storage.py +0 -0
  186. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_fault_tolerance.py +0 -0
  187. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_schedule_storage.py +0 -0
  188. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_singleton.py +0 -0
  189. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_stream_e2e.py +0 -0
  190. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/integration/test_workflow_suspended.py +0 -0
  191. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/__init__.py +0 -0
  192. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/__init__.py +0 -0
  193. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_cassandra_storage.py +0 -0
  194. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_citus_storage.py +0 -0
  195. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_dynamodb_storage.py +0 -0
  196. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_postgres_storage.py +0 -0
  197. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/backends/test_sqlite_storage.py +0 -0
  198. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/conftest.py +0 -0
  199. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/storage/__init__.py +0 -0
  200. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/storage/test_migrations.py +0 -0
  201. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_cancellation.py +0 -0
  202. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_child_workflows.py +0 -0
  203. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_cli_worker.py +0 -0
  204. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_continue_as_new.py +0 -0
  205. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_emit.py +0 -0
  206. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_event_limits.py +0 -0
  207. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_executor.py +0 -0
  208. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_fault_tolerance.py +0 -0
  209. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_force_local.py +0 -0
  210. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_hooks.py +0 -0
  211. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_parent_run_id.py +0 -0
  212. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_primitives_from_steps.py +0 -0
  213. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_registry.py +0 -0
  214. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_replay.py +0 -0
  215. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_retention.py +0 -0
  216. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_schedule_schemas.py +0 -0
  217. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_schedule_utils.py +0 -0
  218. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_scheduled_workflow.py +0 -0
  219. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_signal.py +0 -0
  220. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_singleton.py +0 -0
  221. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step.py +0 -0
  222. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step_checkpoint.py +0 -0
  223. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step_context.py +0 -0
  224. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_step_hook.py +0 -0
  225. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_stream_storage.py +0 -0
  226. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_stream_workflow.py +0 -0
  227. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_validation.py +0 -0
  228. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_workflow.py +0 -0
  229. {pyworkflow_engine-0.2.2 → pyworkflow_engine-0.2.5}/tests/unit/test_workflow_suspended.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyworkflow-engine
3
- Version: 0.2.2
3
+ Version: 0.2.5
4
4
  Summary: A Python implementation of durable, event-sourced workflows inspired by Vercel Workflow
5
5
  Author: PyWorkflow Contributors
6
6
  License: MIT
@@ -7,7 +7,7 @@ packages = [{include = "pyworkflow"}]
7
7
 
8
8
  [project]
9
9
  name = "pyworkflow-engine"
10
- version = "0.2.2"
10
+ version = "0.2.5"
11
11
  description = "A Python implementation of durable, event-sourced workflows inspired by Vercel Workflow"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -29,7 +29,7 @@ Quick Start:
29
29
  >>> run_id = await start(my_workflow, "Alice")
30
30
  """
31
31
 
32
- __version__ = "0.2.2"
32
+ __version__ = "0.2.5"
33
33
 
34
34
  # Configuration
35
35
  from pyworkflow.config import (
@@ -304,33 +304,17 @@ def create_celery_app(
304
304
  # Broker transport options - prevent task redelivery
305
305
  # See: https://github.com/celery/celery/issues/5935
306
306
  "broker_transport_options": final_broker_opts,
307
- # Task routing
307
+ # Task routing — use direct exchange (each task specifies queue= explicitly)
308
308
  "task_default_queue": "pyworkflow.default",
309
309
  "task_default_exchange": "pyworkflow",
310
- "task_default_exchange_type": "topic",
311
- "task_default_routing_key": "workflow.default",
310
+ "task_default_exchange_type": "direct",
311
+ "task_default_routing_key": "pyworkflow.default",
312
312
  # Task queues
313
313
  "task_queues": (
314
- Queue(
315
- "pyworkflow.default",
316
- Exchange("pyworkflow", type="topic"),
317
- routing_key="workflow.#",
318
- ),
319
- Queue(
320
- "pyworkflow.steps",
321
- Exchange("pyworkflow", type="topic"),
322
- routing_key="workflow.step.#",
323
- ),
324
- Queue(
325
- "pyworkflow.workflows",
326
- Exchange("pyworkflow", type="topic"),
327
- routing_key="workflow.workflow.#",
328
- ),
329
- Queue(
330
- "pyworkflow.schedules",
331
- Exchange("pyworkflow", type="topic"),
332
- routing_key="workflow.schedule.#",
333
- ),
314
+ Queue("pyworkflow.default", Exchange("pyworkflow", type="direct")),
315
+ Queue("pyworkflow.steps", Exchange("pyworkflow", type="direct")),
316
+ Queue("pyworkflow.workflows", Exchange("pyworkflow", type="direct")),
317
+ Queue("pyworkflow.schedules", Exchange("pyworkflow", type="direct")),
334
318
  ),
335
319
  # Task execution
336
320
  "task_acks_late": True,
@@ -10,7 +10,6 @@ These tasks enable:
10
10
  """
11
11
 
12
12
  import asyncio
13
- import contextlib
14
13
  import random
15
14
  import uuid
16
15
  from collections.abc import Callable
@@ -186,29 +185,6 @@ def execute_step_task(
186
185
  )
187
186
  return None
188
187
 
189
- # Execution-time guard: prevent duplicate step execution on concurrent workers.
190
- # The singleton lock (unique_on=["run_id", "step_id"]) prevents duplicate queueing,
191
- # but broker-level re-delivery or race conditions can still result in two workers
192
- # executing the same step. This Redis lock ensures only one proceeds.
193
- _exec_lock_key = f"pyworkflow:step_exec:{run_id}:{step_id}"
194
- _exec_lock_backend = self.singleton_backend
195
- _exec_lock_acquired = False
196
- if _exec_lock_backend is not None:
197
- _exec_lock_acquired = _exec_lock_backend.lock(
198
- _exec_lock_key, self.request.id or "unknown", expiry=self._lock_expiry
199
- )
200
- if not _exec_lock_acquired:
201
- logger.warning(
202
- "Step already being executed by another worker, skipping duplicate",
203
- run_id=run_id,
204
- step_id=step_id,
205
- step_name=step_name,
206
- existing_worker_task=_exec_lock_backend.get(_exec_lock_key),
207
- )
208
- return None
209
- else:
210
- _exec_lock_acquired = True # No Redis = no locking
211
-
212
188
  # Deserialize arguments
213
189
  args = deserialize_args(args_json)
214
190
  kwargs = deserialize_kwargs(kwargs_json)
@@ -475,11 +451,6 @@ def execute_step_task(
475
451
  raise FatalError(f"Step '{step_name}' failed after retries: {str(e)}") from e
476
452
 
477
453
  finally:
478
- # Release execution lock so retries or other tasks can proceed.
479
- if _exec_lock_acquired and _exec_lock_backend is not None:
480
- with contextlib.suppress(Exception):
481
- _exec_lock_backend.unlock(_exec_lock_key)
482
-
483
454
  # Clean up step execution context (checkpoint/hook primitives)
484
455
  if step_exec_tokens is not None:
485
456
  from pyworkflow.primitives.step_checkpoint import reset_step_execution_context
@@ -864,62 +835,33 @@ def start_workflow_task(
864
835
  celery_task_id=self.request.id,
865
836
  )
866
837
 
867
- # Execution-time guard: prevent duplicate workflow start on concurrent workers.
868
- # The singleton lock (unique_on=["run_id"]) prevents duplicate queueing, but
869
- # broker-level re-delivery or race conditions can still result in two workers
870
- # executing the same start task. This Redis lock ensures only one proceeds.
871
- _exec_lock_key = f"pyworkflow:wf_start:{run_id}"
872
- _exec_lock_backend = self.singleton_backend
873
- _exec_lock_acquired = False
874
- if _exec_lock_backend is not None:
875
- _exec_lock_acquired = _exec_lock_backend.lock(
876
- _exec_lock_key, self.request.id or "unknown", expiry=self._lock_expiry
877
- )
878
- if not _exec_lock_acquired:
879
- logger.warning(
880
- "Workflow start already being executed by another worker, skipping duplicate",
881
- run_id=run_id,
882
- workflow_name=workflow_name,
883
- existing_worker_task=_exec_lock_backend.get(_exec_lock_key),
884
- )
885
- return run_id
886
- else:
887
- _exec_lock_acquired = True # No Redis = no locking
888
-
889
- try:
890
- # Get workflow metadata
891
- workflow_meta = get_workflow(workflow_name)
892
- if not workflow_meta:
893
- raise ValueError(f"Workflow '{workflow_name}' not found in registry")
838
+ # Get workflow metadata
839
+ workflow_meta = get_workflow(workflow_name)
840
+ if not workflow_meta:
841
+ raise ValueError(f"Workflow '{workflow_name}' not found in registry")
894
842
 
895
- # Deserialize arguments
896
- args = deserialize_args(args_json)
897
- kwargs = deserialize_kwargs(kwargs_json)
843
+ # Deserialize arguments
844
+ args = deserialize_args(args_json)
845
+ kwargs = deserialize_kwargs(kwargs_json)
898
846
 
899
- # Get storage backend
900
- storage = _get_storage_backend(storage_config)
847
+ # Get storage backend
848
+ storage = _get_storage_backend(storage_config)
901
849
 
902
- # Execute workflow directly on worker
903
- result_run_id = run_async(
904
- _start_workflow_on_worker(
905
- workflow_meta=workflow_meta,
906
- args=args,
907
- kwargs=kwargs,
908
- storage=storage,
909
- storage_config=storage_config,
910
- idempotency_key=idempotency_key,
911
- run_id=run_id,
912
- )
850
+ # Execute workflow directly on worker
851
+ result_run_id = run_async(
852
+ _start_workflow_on_worker(
853
+ workflow_meta=workflow_meta,
854
+ args=args,
855
+ kwargs=kwargs,
856
+ storage=storage,
857
+ storage_config=storage_config,
858
+ idempotency_key=idempotency_key,
859
+ run_id=run_id,
913
860
  )
861
+ )
914
862
 
915
- logger.info(f"Workflow execution initiated: {workflow_name}", run_id=result_run_id)
916
- return result_run_id
917
-
918
- finally:
919
- # Release execution lock so retries or re-dispatches can proceed.
920
- if _exec_lock_acquired and _exec_lock_backend is not None:
921
- with contextlib.suppress(Exception):
922
- _exec_lock_backend.unlock(_exec_lock_key)
863
+ logger.info(f"Workflow execution initiated: {workflow_name}", run_id=result_run_id)
864
+ return result_run_id
923
865
 
924
866
 
925
867
  @celery_app.task(
@@ -2520,6 +2462,9 @@ async def _resume_workflow_on_worker(
2520
2462
  run_id=run_id,
2521
2463
  step_id=step_id,
2522
2464
  )
2465
+ resume_workflow_task.release_lock(
2466
+ task_args=[run_id], task_kwargs={"storage_config": storage_config}
2467
+ )
2523
2468
  schedule_workflow_resumption(
2524
2469
  run_id,
2525
2470
  datetime.now(UTC),
@@ -2539,6 +2484,9 @@ async def _resume_workflow_on_worker(
2539
2484
  run_id=run_id,
2540
2485
  hook_id=hook_id,
2541
2486
  )
2487
+ resume_workflow_task.release_lock(
2488
+ task_args=[run_id], task_kwargs={"storage_config": storage_config}
2489
+ )
2542
2490
  schedule_workflow_resumption(
2543
2491
  run_id,
2544
2492
  datetime.now(UTC),
@@ -2550,6 +2498,13 @@ async def _resume_workflow_on_worker(
2550
2498
  # Schedule automatic resumption if we have a resume_at time
2551
2499
  resume_at = e.data.get("resume_at") if e.data else None
2552
2500
  if resume_at:
2501
+ # Release the singleton lock BEFORE scheduling the next resume.
2502
+ # Without this, the new resume_workflow_task.apply_async() sees
2503
+ # the lock still held by the current (calling) task and silently
2504
+ # drops the schedule as a duplicate.
2505
+ resume_workflow_task.release_lock(
2506
+ task_args=[run_id], task_kwargs={"storage_config": storage_config}
2507
+ )
2553
2508
  schedule_workflow_resumption(
2554
2509
  run_id, resume_at, storage_config=storage_config, triggered_by="start_sleep_hook"
2555
2510
  )
@@ -151,7 +151,16 @@ class LocalContext(WorkflowContext):
151
151
  from pyworkflow.engine.events import EventType
152
152
  from pyworkflow.serialization.decoder import deserialize
153
153
 
154
+ max_counter = 0
154
155
  for event in events:
156
+ # Track step counter from sleep and hook IDs to avoid collisions
157
+ for key in ("sleep_id", "hook_id"):
158
+ id_val = event.data.get(key) if event.data else None
159
+ if id_val:
160
+ c = self._extract_counter_from_id(id_val)
161
+ if c > max_counter:
162
+ max_counter = c
163
+
155
164
  if event.type == EventType.STEP_STARTED:
156
165
  # Track step as in-progress (dispatched but not completed)
157
166
  step_id = event.data.get("step_id")
@@ -271,6 +280,10 @@ class LocalContext(WorkflowContext):
271
280
  }
272
281
  self._pending_children.pop(child_id, None)
273
282
 
283
+ # Restore step counter so new sleeps/hooks don't collide with replayed ones
284
+ if max_counter > self._step_counter:
285
+ self._step_counter = max_counter
286
+
274
287
  @property
275
288
  def is_durable(self) -> bool:
276
289
  return self._durable
@@ -669,7 +669,8 @@ async def _dispatch_step_to_celery(
669
669
 
670
670
  # Defense-in-depth: check if STEP_STARTED was already recorded for this step.
671
671
  # This guards against duplicate dispatch when two resume tasks race and both
672
- # replay past the same step. If already started, re-suspend to wait.
672
+ # replay past the same step. If already started, re-suspend to wait for
673
+ # the running task to complete via task_reject_on_worker_lost re-delivery.
673
674
  # Exception: if STEP_SUSPENDED was recorded (step_hook suspension), the step
674
675
  # needs re-dispatch so it can re-execute and find the HOOK_RECEIVED event.
675
676
  events = await ctx.storage.get_events(ctx.run_id)
@@ -682,7 +683,7 @@ async def _dispatch_step_to_celery(
682
683
  )
683
684
  if already_started and not was_suspended:
684
685
  logger.info(
685
- f"Step {step_name} already has STEP_STARTED event, re-suspending",
686
+ f"Step {step_name} already has STEP_STARTED event, re-suspending (task running)",
686
687
  run_id=ctx.run_id,
687
688
  step_id=step_id,
688
689
  )
@@ -755,6 +755,7 @@ class StorageBackend(ABC):
755
755
  signal_type: str,
756
756
  payload: dict,
757
757
  source_run_id: str | None = None,
758
+ stream_run_id: str | None = None,
758
759
  metadata: dict | None = None,
759
760
  ) -> int:
760
761
  """
@@ -766,6 +767,7 @@ class StorageBackend(ABC):
766
767
  signal_type: Signal type (e.g., "task.created")
767
768
  payload: Signal payload data
768
769
  source_run_id: Optional source workflow run ID
770
+ stream_run_id: Optional stream run identifier for grouping signals
769
771
  metadata: Optional signal metadata
770
772
 
771
773
  Returns:
@@ -798,6 +800,40 @@ class StorageBackend(ABC):
798
800
  """
799
801
  raise NotImplementedError("This storage backend does not support streams")
800
802
 
803
+ async def query_stream_signals(
804
+ self,
805
+ stream_id: str,
806
+ stream_run_id: str,
807
+ *,
808
+ source_run_id: str | None = None,
809
+ signal_type: str | None = None,
810
+ after_sequence: int | None = None,
811
+ before_sequence: int | None = None,
812
+ last_n: int | None = None,
813
+ limit: int = 50,
814
+ ) -> list[dict]:
815
+ """
816
+ Query signals from a stream with rich filtering.
817
+
818
+ Args:
819
+ stream_id: Stream identifier.
820
+ source_run_id: Filter to signals from a specific workflow run.
821
+ stream_run_id: Filter to signals from a specific stream run.
822
+ signal_type: Filter by signal type (e.g. ``"task.completed"``).
823
+ after_sequence: Only include signals with ``sequence >= value``.
824
+ before_sequence: Only include signals with ``sequence <= value``.
825
+ last_n: Return the N most recent signals (overrides
826
+ ``after_sequence`` and ``limit``).
827
+ limit: Maximum number of signals to return.
828
+
829
+ Returns:
830
+ List of signal dicts ordered by sequence ASC.
831
+
832
+ Raises:
833
+ NotImplementedError: If backend doesn't support streams.
834
+ """
835
+ raise NotImplementedError("This storage backend does not support streams")
836
+
801
837
  # Subscription Operations
802
838
 
803
839
  async def register_stream_subscription(
@@ -839,6 +875,28 @@ class StorageBackend(ABC):
839
875
  """
840
876
  raise NotImplementedError("This storage backend does not support streams")
841
877
 
878
+ async def get_subscriptions_for_stream(
879
+ self,
880
+ stream_id: str,
881
+ signal_type: str,
882
+ ) -> list[dict]:
883
+ """
884
+ Get ALL subscriptions for a signal type on a stream, regardless of status.
885
+
886
+ Unlike ``get_waiting_steps`` which only returns ``"waiting"``
887
+ subscriptions, this returns subscriptions in any status (waiting,
888
+ running, etc.). Used to avoid creating duplicate subscriptions
889
+ when a step is currently executing.
890
+
891
+ Args:
892
+ stream_id: Stream identifier
893
+ signal_type: Signal type to match
894
+
895
+ Returns:
896
+ List of dicts with step subscription info
897
+ """
898
+ raise NotImplementedError("This storage backend does not support streams")
899
+
842
900
  async def update_subscription_status(
843
901
  self,
844
902
  stream_id: str,