pyworkflow-engine 0.1.21__tar.gz → 0.1.22__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 (191) hide show
  1. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/PKG-INFO +1 -1
  2. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyproject.toml +1 -1
  3. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/__init__.py +1 -1
  4. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/celery/tasks.py +114 -62
  5. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/CLAUDE.md +0 -0
  6. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/DISTRIBUTED.md +0 -0
  7. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/LICENSE +0 -0
  8. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/MANIFEST.in +0 -0
  9. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/README.md +0 -0
  10. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/RELEASING.md +0 -0
  11. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/cancellation.mdx +0 -0
  12. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/continue-as-new.mdx +0 -0
  13. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/events.mdx +0 -0
  14. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/fault-tolerance.mdx +0 -0
  15. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/hooks.mdx +0 -0
  16. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/limitations.mdx +0 -0
  17. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/schedules.mdx +0 -0
  18. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/sleep.mdx +0 -0
  19. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/step-context.mdx +0 -0
  20. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/steps.mdx +0 -0
  21. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/concepts/workflows.mdx +0 -0
  22. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/guides/brokers.mdx +0 -0
  23. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/guides/cli.mdx +0 -0
  24. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/guides/configuration.mdx +0 -0
  25. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/introduction.mdx +0 -0
  26. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/docs/quickstart.mdx +0 -0
  27. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/__init__.py +0 -0
  28. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/__init__.py +0 -0
  29. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/docker-compose.yml +0 -0
  30. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/pyworkflow.config.yaml +0 -0
  31. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/__init__.py +0 -0
  32. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/basic.py +0 -0
  33. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/batch_processing.py +0 -0
  34. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/cancellation.py +0 -0
  35. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/child_workflow_patterns.py +0 -0
  36. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/child_workflows.py +0 -0
  37. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/continue_as_new.py +0 -0
  38. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/fault_tolerance.py +0 -0
  39. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/hooks.py +0 -0
  40. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/idempotency.py +0 -0
  41. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/long_running.py +0 -0
  42. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/retries.py +0 -0
  43. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/schedules.py +0 -0
  44. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/durable/workflows/step_context.py +0 -0
  45. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/transient/01_basic_workflow.py +0 -0
  46. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/transient/02_fault_tolerance.py +0 -0
  47. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/transient/__init__.py +0 -0
  48. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/celery/transient/pyworkflow.config.yaml +0 -0
  49. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/__init__.py +0 -0
  50. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/01_basic_workflow.py +0 -0
  51. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/02_file_storage.py +0 -0
  52. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/03_retries.py +0 -0
  53. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/04_long_running.py +0 -0
  54. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/05_event_log.py +0 -0
  55. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/06_idempotency.py +0 -0
  56. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/07_hooks.py +0 -0
  57. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/08_cancellation.py +0 -0
  58. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/09_child_workflows.py +0 -0
  59. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/10_child_workflow_patterns.py +0 -0
  60. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/11_continue_as_new.py +0 -0
  61. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/12_schedules.py +0 -0
  62. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/13_step_context.py +0 -0
  63. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/durable/__init__.py +0 -0
  64. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/transient/01_quick_tasks.py +0 -0
  65. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/transient/02_retries.py +0 -0
  66. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/transient/03_sleep.py +0 -0
  67. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/examples/local/transient/__init__.py +0 -0
  68. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/aws/__init__.py +0 -0
  69. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/aws/context.py +0 -0
  70. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/aws/handler.py +0 -0
  71. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/aws/testing.py +0 -0
  72. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/celery/__init__.py +0 -0
  73. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/celery/app.py +0 -0
  74. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/celery/loop.py +0 -0
  75. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/celery/scheduler.py +0 -0
  76. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/celery/singleton.py +0 -0
  77. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/__init__.py +0 -0
  78. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/__main__.py +0 -0
  79. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/__init__.py +0 -0
  80. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/hooks.py +0 -0
  81. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/quickstart.py +0 -0
  82. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/runs.py +0 -0
  83. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/scheduler.py +0 -0
  84. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/schedules.py +0 -0
  85. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/setup.py +0 -0
  86. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/worker.py +0 -0
  87. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/commands/workflows.py +0 -0
  88. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/output/__init__.py +0 -0
  89. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/output/formatters.py +0 -0
  90. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/output/styles.py +0 -0
  91. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/__init__.py +0 -0
  92. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/async_helpers.py +0 -0
  93. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/config.py +0 -0
  94. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/config_generator.py +0 -0
  95. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/discovery.py +0 -0
  96. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/docker_manager.py +0 -0
  97. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/interactive.py +0 -0
  98. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/cli/utils/storage.py +0 -0
  99. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/config.py +0 -0
  100. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/context/__init__.py +0 -0
  101. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/context/aws.py +0 -0
  102. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/context/base.py +0 -0
  103. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/context/local.py +0 -0
  104. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/context/mock.py +0 -0
  105. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/context/step_context.py +0 -0
  106. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/__init__.py +0 -0
  107. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/exceptions.py +0 -0
  108. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/registry.py +0 -0
  109. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/scheduled.py +0 -0
  110. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/step.py +0 -0
  111. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/validation.py +0 -0
  112. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/core/workflow.py +0 -0
  113. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/discovery.py +0 -0
  114. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/engine/__init__.py +0 -0
  115. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/engine/events.py +0 -0
  116. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/engine/executor.py +0 -0
  117. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/engine/replay.py +0 -0
  118. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/observability/__init__.py +0 -0
  119. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/observability/logging.py +0 -0
  120. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/__init__.py +0 -0
  121. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/child_handle.py +0 -0
  122. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/child_workflow.py +0 -0
  123. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/continue_as_new.py +0 -0
  124. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/define_hook.py +0 -0
  125. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/hooks.py +0 -0
  126. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/resume_hook.py +0 -0
  127. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/schedule.py +0 -0
  128. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/shield.py +0 -0
  129. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/primitives/sleep.py +0 -0
  130. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/runtime/__init__.py +0 -0
  131. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/runtime/base.py +0 -0
  132. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/runtime/celery.py +0 -0
  133. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/runtime/factory.py +0 -0
  134. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/runtime/local.py +0 -0
  135. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/scheduler/__init__.py +0 -0
  136. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/scheduler/local.py +0 -0
  137. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/serialization/__init__.py +0 -0
  138. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/serialization/decoder.py +0 -0
  139. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/serialization/encoder.py +0 -0
  140. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/__init__.py +0 -0
  141. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/base.py +0 -0
  142. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/cassandra.py +0 -0
  143. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/config.py +0 -0
  144. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/dynamodb.py +0 -0
  145. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/file.py +0 -0
  146. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/memory.py +0 -0
  147. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/mysql.py +0 -0
  148. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/postgres.py +0 -0
  149. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/schemas.py +0 -0
  150. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/storage/sqlite.py +0 -0
  151. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/utils/__init__.py +0 -0
  152. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/utils/duration.py +0 -0
  153. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow/utils/schedule.py +0 -0
  154. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/pyworkflow_engine.egg-info/SOURCES.txt +0 -0
  155. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/setup.cfg +0 -0
  156. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/__init__.py +0 -0
  157. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_cancellation.py +0 -0
  158. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_cassandra_storage.py +0 -0
  159. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_child_workflows.py +0 -0
  160. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_continue_as_new.py +0 -0
  161. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_dynamodb_storage.py +0 -0
  162. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_fault_tolerance.py +0 -0
  163. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_schedule_storage.py +0 -0
  164. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_singleton.py +0 -0
  165. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/integration/test_workflow_suspended.py +0 -0
  166. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/__init__.py +0 -0
  167. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/backends/__init__.py +0 -0
  168. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/backends/test_cassandra_storage.py +0 -0
  169. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/backends/test_dynamodb_storage.py +0 -0
  170. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/backends/test_postgres_storage.py +0 -0
  171. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/backends/test_sqlite_storage.py +0 -0
  172. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/conftest.py +0 -0
  173. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_cancellation.py +0 -0
  174. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_child_workflows.py +0 -0
  175. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_cli_worker.py +0 -0
  176. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_continue_as_new.py +0 -0
  177. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_event_limits.py +0 -0
  178. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_executor.py +0 -0
  179. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_fault_tolerance.py +0 -0
  180. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_hooks.py +0 -0
  181. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_registry.py +0 -0
  182. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_replay.py +0 -0
  183. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_schedule_schemas.py +0 -0
  184. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_schedule_utils.py +0 -0
  185. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_scheduled_workflow.py +0 -0
  186. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_singleton.py +0 -0
  187. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_step.py +0 -0
  188. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_step_context.py +0 -0
  189. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_validation.py +0 -0
  190. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/tests/unit/test_workflow.py +0 -0
  191. {pyworkflow_engine-0.1.21 → pyworkflow_engine-0.1.22}/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.1.21
3
+ Version: 0.1.22
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.1.21"
10
+ version = "0.1.22"
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.1.21"
32
+ __version__ = "0.1.22"
33
33
 
34
34
  # Configuration
35
35
  from pyworkflow.config import (
@@ -11,6 +11,7 @@ These tasks enable:
11
11
 
12
12
  import asyncio
13
13
  import random
14
+ import traceback
14
15
  import uuid
15
16
  from collections.abc import Callable
16
17
  from datetime import UTC, datetime
@@ -379,9 +380,9 @@ async def _record_step_completion_and_resume(
379
380
 
380
381
  Called by execute_step_task after successful step execution.
381
382
 
382
- Only schedules resume if WORKFLOW_SUSPENDED event exists, indicating
383
- the workflow has fully suspended. This prevents race conditions where
384
- a step completes before the workflow has suspended.
383
+ IMPORTANT: This function waits for WORKFLOW_SUSPENDED event before recording
384
+ STEP_COMPLETED to prevent race conditions where both events get the same
385
+ sequence number. The workflow must fully suspend before we record completion.
385
386
 
386
387
  Idempotency: If STEP_COMPLETED already exists for this step_id, skip
387
388
  recording and resume scheduling (another task already handled it).
@@ -411,43 +412,67 @@ async def _record_step_completion_and_resume(
411
412
  )
412
413
  return
413
414
 
414
- # Record STEP_COMPLETED event
415
- completion_event = create_step_completed_event(
416
- run_id=run_id,
417
- step_id=step_id,
418
- result=serialize(result),
419
- step_name=step_name,
420
- )
421
- await storage.record_event(completion_event)
415
+ # Wait for WORKFLOW_SUSPENDED event before recording STEP_COMPLETED
416
+ # This prevents race conditions where both events get the same sequence number
417
+ max_wait_attempts = 50 # 50 * 10ms = 500ms max wait
418
+ wait_interval = 0.01 # 10ms between checks
422
419
 
423
- # Refresh events to include the one we just recorded
424
- events = await storage.get_events(run_id)
420
+ for attempt in range(max_wait_attempts):
421
+ has_suspended = any(
422
+ evt.type == EventType.WORKFLOW_SUSPENDED
423
+ and evt.data.get("step_id") == step_id
424
+ for evt in events
425
+ )
426
+ if has_suspended:
427
+ break
425
428
 
426
- # Check if workflow has suspended (WORKFLOW_SUSPENDED event exists)
427
- # Only schedule resume if workflow has properly suspended
428
- has_suspended = any(evt.type == EventType.WORKFLOW_SUSPENDED for evt in events)
429
+ # Wait and refresh events
430
+ await asyncio.sleep(wait_interval)
431
+ events = await storage.get_events(run_id)
429
432
 
430
- if has_suspended:
431
- # Workflow has suspended, safe to schedule resume
432
- schedule_workflow_resumption(
433
- run_id, datetime.now(UTC), storage_config, triggered_by="step_completed"
434
- )
435
- logger.info(
436
- "Step completed and workflow resumption scheduled",
437
- run_id=run_id,
438
- step_id=step_id,
439
- step_name=step_name,
433
+ # Also check if step was already completed by another task during wait
434
+ already_completed = any(
435
+ evt.type == EventType.STEP_COMPLETED and evt.data.get("step_id") == step_id
436
+ for evt in events
440
437
  )
438
+ if already_completed:
439
+ logger.info(
440
+ "Step already completed by another task during wait, skipping",
441
+ run_id=run_id,
442
+ step_id=step_id,
443
+ step_name=step_name,
444
+ )
445
+ return
441
446
  else:
442
- # Workflow hasn't suspended yet - don't schedule resume
443
- # The suspension handler will check for step completion and schedule resume
444
- logger.info(
445
- "Step completed but workflow not yet suspended, skipping resume scheduling",
447
+ # Timeout waiting for suspension - log warning but proceed anyway
448
+ # This handles edge cases where the workflow completes without suspending
449
+ logger.warning(
450
+ "Timeout waiting for WORKFLOW_SUSPENDED event, proceeding with completion",
446
451
  run_id=run_id,
447
452
  step_id=step_id,
448
453
  step_name=step_name,
449
454
  )
450
455
 
456
+ # Record STEP_COMPLETED event
457
+ completion_event = create_step_completed_event(
458
+ run_id=run_id,
459
+ step_id=step_id,
460
+ result=serialize(result),
461
+ step_name=step_name,
462
+ )
463
+ await storage.record_event(completion_event)
464
+
465
+ # Schedule workflow resumption
466
+ schedule_workflow_resumption(
467
+ run_id, datetime.now(UTC), storage_config, triggered_by="step_completed"
468
+ )
469
+ logger.info(
470
+ "Step completed and workflow resumption scheduled",
471
+ run_id=run_id,
472
+ step_id=step_id,
473
+ step_name=step_name,
474
+ )
475
+
451
476
 
452
477
  async def _record_step_failure_and_resume(
453
478
  storage_config: dict[str, Any] | None,
@@ -464,9 +489,9 @@ async def _record_step_failure_and_resume(
464
489
  Called by execute_step_task after step failure (when retries are exhausted).
465
490
  The workflow will fail when it replays and sees the failure event.
466
491
 
467
- Only schedules resume if WORKFLOW_SUSPENDED event exists, indicating
468
- the workflow has fully suspended. This prevents race conditions where
469
- a step fails before the workflow has suspended.
492
+ IMPORTANT: This function waits for WORKFLOW_SUSPENDED event before recording
493
+ STEP_FAILED to prevent race conditions where both events get the same
494
+ sequence number. The workflow must fully suspend before we record failure.
470
495
 
471
496
  Idempotency: If STEP_COMPLETED or terminal STEP_FAILED already exists
472
497
  for this step_id, skip recording and resume scheduling.
@@ -500,6 +525,51 @@ async def _record_step_failure_and_resume(
500
525
  )
501
526
  return
502
527
 
528
+ # Wait for WORKFLOW_SUSPENDED event before recording STEP_FAILED
529
+ # This prevents race conditions where both events get the same sequence number
530
+ max_wait_attempts = 50 # 50 * 10ms = 500ms max wait
531
+ wait_interval = 0.01 # 10ms between checks
532
+
533
+ for attempt in range(max_wait_attempts):
534
+ has_suspended = any(
535
+ evt.type == EventType.WORKFLOW_SUSPENDED
536
+ and evt.data.get("step_id") == step_id
537
+ for evt in events
538
+ )
539
+ if has_suspended:
540
+ break
541
+
542
+ # Wait and refresh events
543
+ await asyncio.sleep(wait_interval)
544
+ events = await storage.get_events(run_id)
545
+
546
+ # Also check if step was already handled by another task during wait
547
+ already_handled = any(
548
+ (evt.type == EventType.STEP_COMPLETED and evt.data.get("step_id") == step_id)
549
+ or (
550
+ evt.type == EventType.STEP_FAILED
551
+ and evt.data.get("step_id") == step_id
552
+ and not evt.data.get("is_retryable", True)
553
+ )
554
+ for evt in events
555
+ )
556
+ if already_handled:
557
+ logger.info(
558
+ "Step already completed/failed by another task during wait, skipping",
559
+ run_id=run_id,
560
+ step_id=step_id,
561
+ step_name=step_name,
562
+ )
563
+ return
564
+ else:
565
+ # Timeout waiting for suspension - log warning but proceed anyway
566
+ logger.warning(
567
+ "Timeout waiting for WORKFLOW_SUSPENDED event, proceeding with failure",
568
+ run_id=run_id,
569
+ step_id=step_id,
570
+ step_name=step_name,
571
+ )
572
+
503
573
  # Record STEP_FAILED event
504
574
  failure_event = create_step_failed_event(
505
575
  run_id=run_id,
@@ -511,35 +581,17 @@ async def _record_step_failure_and_resume(
511
581
  )
512
582
  await storage.record_event(failure_event)
513
583
 
514
- # Refresh events to include the one we just recorded
515
- events = await storage.get_events(run_id)
516
-
517
- # Check if workflow has suspended (WORKFLOW_SUSPENDED event exists)
518
- # Only schedule resume if workflow has properly suspended
519
- has_suspended = any(evt.type == EventType.WORKFLOW_SUSPENDED for evt in events)
520
-
521
- if has_suspended:
522
- # Workflow has suspended, safe to schedule resume
523
- schedule_workflow_resumption(
524
- run_id, datetime.now(UTC), storage_config, triggered_by="step_failed"
525
- )
526
- logger.info(
527
- "Step failed and workflow resumption scheduled",
528
- run_id=run_id,
529
- step_id=step_id,
530
- step_name=step_name,
531
- error=error,
532
- )
533
- else:
534
- # Workflow hasn't suspended yet - don't schedule resume
535
- # The suspension handler will check for step failure and schedule resume
536
- logger.info(
537
- "Step failed but workflow not yet suspended, skipping resume scheduling",
538
- run_id=run_id,
539
- step_id=step_id,
540
- step_name=step_name,
541
- error=error,
542
- )
584
+ # Schedule workflow resumption
585
+ schedule_workflow_resumption(
586
+ run_id, datetime.now(UTC), storage_config, triggered_by="step_failed"
587
+ )
588
+ logger.info(
589
+ "Step failed and workflow resumption scheduled",
590
+ run_id=run_id,
591
+ step_id=step_id,
592
+ step_name=step_name,
593
+ error=error,
594
+ )
543
595
 
544
596
 
545
597
  async def _get_workflow_run_safe(