pyworkflow-engine 0.3.7__tar.gz → 0.4.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 (251) hide show
  1. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/DISTRIBUTED.md +4 -1
  2. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/PKG-INFO +50 -44
  3. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/README.md +30 -24
  4. pyworkflow_engine-0.4.0/docs/concepts/step-hooks-and-checkpointing.mdx +325 -0
  5. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/conventions.md +12 -2
  6. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyproject.toml +20 -20
  7. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/__init__.py +7 -1
  8. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/tasks.py +85 -7
  9. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/context/base.py +58 -0
  10. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/context/local.py +48 -0
  11. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/registry.py +17 -1
  12. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/step.py +41 -16
  13. pyworkflow_engine-0.4.0/pyworkflow/core/strategy.py +74 -0
  14. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/workflow.py +21 -0
  15. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/engine/executor.py +107 -0
  16. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/step_checkpoint.py +27 -2
  17. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/step_hook.py +19 -3
  18. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/runtime/base.py +4 -0
  19. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/runtime/celery.py +3 -0
  20. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/runtime/local.py +70 -0
  21. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/memory.py +5 -2
  22. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow_engine.egg-info/SOURCES.txt +8 -0
  23. pyworkflow_engine-0.4.0/tests/integration/_run_strategy_e2e_app.py +216 -0
  24. pyworkflow_engine-0.4.0/tests/integration/_run_strategy_e2e_driver.py +79 -0
  25. pyworkflow_engine-0.4.0/tests/integration/test_run_strategy_e2e.py +479 -0
  26. pyworkflow_engine-0.4.0/tests/integration/test_step_hook_inline_suspension_e2e.py +285 -0
  27. pyworkflow_engine-0.4.0/tests/unit/test_child_completion_notify.py +64 -0
  28. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_step_hook.py +363 -0
  29. pyworkflow_engine-0.4.0/tests/unit/test_workflow_run_strategy.py +621 -0
  30. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/CLAUDE.md +0 -0
  31. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/LICENSE +0 -0
  32. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/MANIFEST.in +0 -0
  33. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/RELEASING.md +0 -0
  34. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/architecture.md +0 -0
  35. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/cancellation.mdx +0 -0
  36. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/continue-as-new.mdx +0 -0
  37. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/events.mdx +0 -0
  38. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/fault-tolerance.mdx +0 -0
  39. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/hooks.mdx +0 -0
  40. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/limitations.mdx +0 -0
  41. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/schedules.mdx +0 -0
  42. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/sleep.mdx +0 -0
  43. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/step-context.mdx +0 -0
  44. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/steps.mdx +0 -0
  45. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/stream-steps.mdx +0 -0
  46. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/stream-workflows.mdx +0 -0
  47. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/concepts/workflows.mdx +0 -0
  48. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/guides/brokers.mdx +0 -0
  49. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/guides/cli.mdx +0 -0
  50. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/guides/configuration.mdx +0 -0
  51. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/harness-gaps.md +0 -0
  52. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/introduction.mdx +0 -0
  53. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/layers.md +0 -0
  54. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/docs/quickstart.mdx +0 -0
  55. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/__init__.py +0 -0
  56. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/__init__.py +0 -0
  57. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/docker-compose.yml +0 -0
  58. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/pyworkflow.config.yaml +0 -0
  59. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/__init__.py +0 -0
  60. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/basic.py +0 -0
  61. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/batch_processing.py +0 -0
  62. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/cancellation.py +0 -0
  63. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/child_workflow_from_step.py +0 -0
  64. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/child_workflow_patterns.py +0 -0
  65. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/child_workflows.py +0 -0
  66. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/continue_as_new.py +0 -0
  67. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/fault_tolerance.py +0 -0
  68. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/hooks.py +0 -0
  69. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/idempotency.py +0 -0
  70. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/long_running.py +0 -0
  71. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/retries.py +0 -0
  72. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/schedules.py +0 -0
  73. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/sleep_in_step.py +0 -0
  74. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/durable/workflows/step_context.py +0 -0
  75. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/transient/01_basic_workflow.py +0 -0
  76. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/transient/02_fault_tolerance.py +0 -0
  77. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/transient/__init__.py +0 -0
  78. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/celery/transient/pyworkflow.config.yaml +0 -0
  79. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/__init__.py +0 -0
  80. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/01_basic_workflow.py +0 -0
  81. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/02_file_storage.py +0 -0
  82. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/03_retries.py +0 -0
  83. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/04_long_running.py +0 -0
  84. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/05_event_log.py +0 -0
  85. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/06_idempotency.py +0 -0
  86. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/07_hooks.py +0 -0
  87. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/08_cancellation.py +0 -0
  88. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/09_child_workflows.py +0 -0
  89. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/10_child_workflow_patterns.py +0 -0
  90. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/11_continue_as_new.py +0 -0
  91. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/12_schedules.py +0 -0
  92. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/13_step_context.py +0 -0
  93. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/14_child_workflow_from_step.py +0 -0
  94. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/durable/__init__.py +0 -0
  95. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/transient/01_quick_tasks.py +0 -0
  96. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/transient/02_retries.py +0 -0
  97. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/transient/03_sleep.py +0 -0
  98. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/examples/local/transient/__init__.py +0 -0
  99. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/aws/__init__.py +0 -0
  100. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/aws/context.py +0 -0
  101. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/aws/handler.py +0 -0
  102. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/aws/testing.py +0 -0
  103. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/__init__.py +0 -0
  104. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/app.py +0 -0
  105. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/loop.py +0 -0
  106. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/reschedule.py +0 -0
  107. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/scheduler.py +0 -0
  108. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/celery/singleton.py +0 -0
  109. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/__init__.py +0 -0
  110. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/__main__.py +0 -0
  111. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/__init__.py +0 -0
  112. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/hooks.py +0 -0
  113. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/quickstart.py +0 -0
  114. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/runs.py +0 -0
  115. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/scheduler.py +0 -0
  116. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/schedules.py +0 -0
  117. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/setup.py +0 -0
  118. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/worker.py +0 -0
  119. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/commands/workflows.py +0 -0
  120. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/output/__init__.py +0 -0
  121. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/output/formatters.py +0 -0
  122. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/output/styles.py +0 -0
  123. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/__init__.py +0 -0
  124. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/async_helpers.py +0 -0
  125. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/config.py +0 -0
  126. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/config_generator.py +0 -0
  127. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/discovery.py +0 -0
  128. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/docker_manager.py +0 -0
  129. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/interactive.py +0 -0
  130. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/cli/utils/storage.py +0 -0
  131. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/config.py +0 -0
  132. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/context/__init__.py +0 -0
  133. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/context/aws.py +0 -0
  134. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/context/mock.py +0 -0
  135. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/context/step_context.py +0 -0
  136. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/__init__.py +0 -0
  137. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/exceptions.py +0 -0
  138. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/scheduled.py +0 -0
  139. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/core/validation.py +0 -0
  140. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/discovery.py +0 -0
  141. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/engine/__init__.py +0 -0
  142. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/engine/events.py +0 -0
  143. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/engine/replay.py +0 -0
  144. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/observability/__init__.py +0 -0
  145. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/observability/logging.py +0 -0
  146. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/__init__.py +0 -0
  147. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/child_handle.py +0 -0
  148. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/child_workflow.py +0 -0
  149. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/continue_as_new.py +0 -0
  150. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/define_hook.py +0 -0
  151. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/hooks.py +0 -0
  152. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/resume_hook.py +0 -0
  153. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/schedule.py +0 -0
  154. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/shield.py +0 -0
  155. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/primitives/sleep.py +0 -0
  156. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/runtime/__init__.py +0 -0
  157. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/runtime/factory.py +0 -0
  158. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/scheduler/__init__.py +0 -0
  159. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/scheduler/local.py +0 -0
  160. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/serialization/__init__.py +0 -0
  161. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/serialization/decoder.py +0 -0
  162. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/serialization/encoder.py +0 -0
  163. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/__init__.py +0 -0
  164. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/base.py +0 -0
  165. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/cassandra.py +0 -0
  166. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/citus.py +0 -0
  167. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/config.py +0 -0
  168. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/dynamodb.py +0 -0
  169. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/file.py +0 -0
  170. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/migrations/__init__.py +0 -0
  171. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/migrations/base.py +0 -0
  172. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/mysql.py +0 -0
  173. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/postgres.py +0 -0
  174. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/schemas.py +0 -0
  175. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/storage/sqlite.py +0 -0
  176. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/__init__.py +0 -0
  177. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/checkpoint.py +0 -0
  178. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/consumer.py +0 -0
  179. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/context.py +0 -0
  180. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/decorator.py +0 -0
  181. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/dispatcher.py +0 -0
  182. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/emit.py +0 -0
  183. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/registry.py +0 -0
  184. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/runtime.py +0 -0
  185. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/signal.py +0 -0
  186. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/streams/step_context.py +0 -0
  187. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/tracing/__init__.py +0 -0
  188. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/tracing/base.py +0 -0
  189. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/tracing/factory.py +0 -0
  190. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/tracing/langfuse.py +0 -0
  191. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/tracing/types.py +0 -0
  192. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/utils/__init__.py +0 -0
  193. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/utils/duration.py +0 -0
  194. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/utils/helpers.py +0 -0
  195. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/pyworkflow/utils/schedule.py +0 -0
  196. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/setup.cfg +0 -0
  197. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/__init__.py +0 -0
  198. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/_reschedule_e2e_app.py +0 -0
  199. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_cancellation.py +0 -0
  200. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_cassandra_storage.py +0 -0
  201. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_child_workflows.py +0 -0
  202. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_continue_as_new.py +0 -0
  203. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_dynamodb_storage.py +0 -0
  204. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_fault_tolerance.py +0 -0
  205. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_list_runs_pagination.py +0 -0
  206. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_reschedule.py +0 -0
  207. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_reschedule_e2e.py +0 -0
  208. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_schedule_storage.py +0 -0
  209. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_schema_migrations.py +0 -0
  210. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_singleton.py +0 -0
  211. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_stream_e2e.py +0 -0
  212. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/integration/test_workflow_suspended.py +0 -0
  213. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/__init__.py +0 -0
  214. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/backends/__init__.py +0 -0
  215. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/backends/test_cassandra_storage.py +0 -0
  216. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/backends/test_citus_storage.py +0 -0
  217. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/backends/test_dynamodb_storage.py +0 -0
  218. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/backends/test_postgres_storage.py +0 -0
  219. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/backends/test_sqlite_storage.py +0 -0
  220. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/conftest.py +0 -0
  221. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/storage/__init__.py +0 -0
  222. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/storage/test_migrations.py +0 -0
  223. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_cancellation.py +0 -0
  224. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_child_workflows.py +0 -0
  225. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_cli_worker.py +0 -0
  226. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_continue_as_new.py +0 -0
  227. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_emit.py +0 -0
  228. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_event_limits.py +0 -0
  229. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_executor.py +0 -0
  230. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_fault_tolerance.py +0 -0
  231. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_force_local.py +0 -0
  232. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_hooks.py +0 -0
  233. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_parent_run_id.py +0 -0
  234. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_primitives_from_steps.py +0 -0
  235. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_registry.py +0 -0
  236. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_replay.py +0 -0
  237. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_reschedule.py +0 -0
  238. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_retention.py +0 -0
  239. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_schedule_schemas.py +0 -0
  240. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_schedule_utils.py +0 -0
  241. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_scheduled_workflow.py +0 -0
  242. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_signal.py +0 -0
  243. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_singleton.py +0 -0
  244. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_step.py +0 -0
  245. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_step_checkpoint.py +0 -0
  246. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_step_context.py +0 -0
  247. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_stream_storage.py +0 -0
  248. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_stream_workflow.py +0 -0
  249. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_validation.py +0 -0
  250. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_workflow.py +0 -0
  251. {pyworkflow_engine-0.3.7 → pyworkflow_engine-0.4.0}/tests/unit/test_workflow_suspended.py +0 -0
@@ -87,17 +87,20 @@ celery -A pyworkflow.celery.app beat --loglevel=info
87
87
  ```python
88
88
  from pyworkflow import workflow, step, start, sleep
89
89
 
90
+
90
91
  @step()
91
92
  async def process_item(item_id: int):
92
93
  # This runs on any available worker
93
94
  return f"Processed {item_id}"
94
95
 
96
+
95
97
  @workflow
96
98
  async def my_workflow(item_id: int):
97
99
  result = await process_item(item_id)
98
100
  await sleep("5m") # Automatically resumes after 5 minutes!
99
101
  return result
100
102
 
103
+
101
104
  # Start workflow - executes across workers
102
105
  run_id = start(my_workflow, item_id=123)
103
106
  ```
@@ -233,7 +236,7 @@ from pyworkflow.celery import create_celery_app
233
236
  app = create_celery_app(
234
237
  broker_url="redis://redis-cluster:6379/0",
235
238
  result_backend="redis://redis-cluster:6379/1",
236
- app_name="my_workflows"
239
+ app_name="my_workflows",
237
240
  )
238
241
 
239
242
  # Additional configuration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyworkflow-engine
3
- Version: 0.3.7
3
+ Version: 0.4.0
4
4
  Summary: A Python implementation of durable, event-sourced workflows inspired by Vercel Workflow
5
5
  Author: PyWorkflow Contributors
6
6
  License: MIT
@@ -26,50 +26,50 @@ Requires-Dist: celery<6.0.0,>=5.3.0
26
26
  Requires-Dist: cloudpickle>=3.0.0
27
27
  Requires-Dist: pydantic<3.0.0,>=2.0.0
28
28
  Requires-Dist: loguru>=0.7.0
29
- Requires-Dist: click>=8.4.1
29
+ Requires-Dist: click>=8.4.2
30
30
  Requires-Dist: inquirerpy>=0.3.4; python_version < "4.0"
31
31
  Requires-Dist: httpx>=0.25.0
32
32
  Requires-Dist: python-dateutil>=2.8.0
33
- Requires-Dist: filelock>=3.29.3
33
+ Requires-Dist: filelock>=3.32.4
34
34
  Requires-Dist: pyyaml>=6.0.0
35
- Requires-Dist: croniter>=2.0.0
35
+ Requires-Dist: croniter>=6.2.4
36
36
  Provides-Extra: redis
37
- Requires-Dist: redis>=5.0.0; extra == "redis"
37
+ Requires-Dist: redis>=8.1.0; extra == "redis"
38
38
  Provides-Extra: sqlite
39
39
  Requires-Dist: aiosqlite>=0.19.0; extra == "sqlite"
40
40
  Provides-Extra: postgres
41
41
  Requires-Dist: asyncpg>=0.29.0; extra == "postgres"
42
42
  Provides-Extra: aws
43
- Requires-Dist: aws-durable-execution-sdk-python>=0.1.0; extra == "aws"
43
+ Requires-Dist: aws-durable-execution-sdk-python>=1.6.0; extra == "aws"
44
44
  Provides-Extra: dynamodb
45
- Requires-Dist: aiobotocore>=3.7.0; extra == "dynamodb"
45
+ Requires-Dist: aiobotocore>=3.9.0; extra == "dynamodb"
46
46
  Provides-Extra: cassandra
47
- Requires-Dist: cassandra-driver>=3.29.0; extra == "cassandra"
47
+ Requires-Dist: cassandra-driver>=3.30.1; extra == "cassandra"
48
48
  Provides-Extra: mysql
49
49
  Requires-Dist: aiomysql>=0.2.0; extra == "mysql"
50
50
  Provides-Extra: all
51
- Requires-Dist: redis>=5.0.0; extra == "all"
51
+ Requires-Dist: redis>=8.1.0; extra == "all"
52
52
  Requires-Dist: aiosqlite>=0.19.0; extra == "all"
53
53
  Requires-Dist: asyncpg>=0.29.0; extra == "all"
54
54
  Requires-Dist: aiomysql>=0.2.0; extra == "all"
55
- Requires-Dist: aws-durable-execution-sdk-python>=0.1.0; extra == "all"
56
- Requires-Dist: cassandra-driver>=3.29.0; extra == "all"
55
+ Requires-Dist: aws-durable-execution-sdk-python>=1.6.0; extra == "all"
56
+ Requires-Dist: cassandra-driver>=3.30.1; extra == "all"
57
57
  Provides-Extra: dev
58
- Requires-Dist: pytest>=7.4.0; extra == "dev"
58
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
59
59
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
60
60
  Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
61
61
  Requires-Dist: pytest-celery>=0.0.0; extra == "dev"
62
62
  Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
63
- Requires-Dist: moto[dynamodb]>=5.2.2; extra == "dev"
63
+ Requires-Dist: moto[dynamodb]>=5.2.3; extra == "dev"
64
64
  Requires-Dist: black>=26.5.1; extra == "dev"
65
- Requires-Dist: ruff>=0.15.17; extra == "dev"
66
- Requires-Dist: mypy>=2.1.0; extra == "dev"
67
- Requires-Dist: pre-commit>=4.6.0; extra == "dev"
65
+ Requires-Dist: ruff>=0.16.4; extra == "dev"
66
+ Requires-Dist: mypy>=2.3.1; extra == "dev"
67
+ Requires-Dist: pre-commit>=4.6.2; extra == "dev"
68
68
  Requires-Dist: types-redis>=4.6.0; extra == "dev"
69
- Requires-Dist: types-python-dateutil>=2.8.0; extra == "dev"
70
- Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
71
- Requires-Dist: flower>=2.0.0; extra == "dev"
72
- Requires-Dist: redis>=5.0.0; extra == "dev"
69
+ Requires-Dist: types-python-dateutil>=2.9.0.20260807; extra == "dev"
70
+ Requires-Dist: types-PyYAML>=6.0.12.20260815; extra == "dev"
71
+ Requires-Dist: flower>=2.1.0; extra == "dev"
72
+ Requires-Dist: redis>=8.1.0; extra == "dev"
73
73
  Requires-Dist: aiosqlite>=0.19.0; extra == "dev"
74
74
  Requires-Dist: asyncpg>=0.29.0; extra == "dev"
75
75
  Dynamic: license-file
@@ -172,17 +172,20 @@ See [DISTRIBUTED.md](DISTRIBUTED.md) for complete deployment guide.
172
172
  ```python
173
173
  from pyworkflow import workflow, step, start, sleep
174
174
 
175
+
175
176
  @step()
176
177
  async def send_welcome_email(user_id: str):
177
178
  # This runs on any available Celery worker
178
179
  print(f"Sending welcome email to user {user_id}")
179
180
  return f"Email sent to {user_id}"
180
181
 
182
+
181
183
  @step()
182
184
  async def send_tips_email(user_id: str):
183
185
  print(f"Sending tips email to user {user_id}")
184
186
  return f"Tips sent to {user_id}"
185
187
 
188
+
186
189
  @workflow()
187
190
  async def onboarding_workflow(user_id: str):
188
191
  # Send welcome email immediately
@@ -196,6 +199,7 @@ async def onboarding_workflow(user_id: str):
196
199
 
197
200
  return "Onboarding complete"
198
201
 
202
+
199
203
  # Start workflow - executes across Celery workers
200
204
  run_id = start(onboarding_workflow, user_id="user_123")
201
205
  print(f"Workflow started: {run_id}")
@@ -221,6 +225,7 @@ Workflows are the top-level orchestration functions. They coordinate steps, hand
221
225
  ```python
222
226
  from pyworkflow import workflow, start
223
227
 
228
+
224
229
  @workflow(name="process_order", max_duration="1h")
225
230
  async def process_order(order_id: str):
226
231
  """
@@ -239,6 +244,7 @@ async def process_order(order_id: str):
239
244
 
240
245
  return {"order_id": order_id, "status": "completed"}
241
246
 
247
+
242
248
  # Start the workflow
243
249
  run_id = start(process_order, order_id="ORD-123")
244
250
  ```
@@ -250,6 +256,7 @@ Steps are the building blocks of workflows. Each step is an isolated, retryable
250
256
  ```python
251
257
  from pyworkflow import step, RetryableError, FatalError
252
258
 
259
+
253
260
  @step(max_retries=5, retry_delay="exponential")
254
261
  async def call_external_api(url: str):
255
262
  """
@@ -281,11 +288,13 @@ By default, steps in a Celery runtime are dispatched to worker processes via the
281
288
  ```python
282
289
  from pyworkflow import step
283
290
 
291
+
284
292
  @step(force_local=True)
285
293
  async def quick_transform(data: dict):
286
294
  """Runs inline even when runtime is Celery."""
287
295
  return {k: v.upper() for k, v in data.items()}
288
296
 
297
+
289
298
  @step()
290
299
  async def heavy_computation(data: dict):
291
300
  """Dispatched to a Celery worker as usual."""
@@ -311,6 +320,7 @@ Workflows can sleep for any duration. During sleep, the workflow suspends and co
311
320
  ```python
312
321
  from pyworkflow import workflow, sleep
313
322
 
323
+
314
324
  @workflow()
315
325
  async def scheduled_reminder(user_id: str):
316
326
  # Send immediate reminder
@@ -407,35 +417,33 @@ Use Python's native `asyncio.gather()` for parallel step execution:
407
417
  import asyncio
408
418
  from pyworkflow import workflow, step
409
419
 
420
+
410
421
  @step()
411
422
  async def fetch_user(user_id: str):
412
423
  # Fetch user data
413
424
  return {"id": user_id, "name": "Alice"}
414
425
 
426
+
415
427
  @step()
416
428
  async def fetch_orders(user_id: str):
417
429
  # Fetch user orders
418
430
  return [{"id": "ORD-1"}, {"id": "ORD-2"}]
419
431
 
432
+
420
433
  @step()
421
434
  async def fetch_recommendations(user_id: str):
422
435
  # Fetch recommendations
423
436
  return ["Product A", "Product B"]
424
437
 
438
+
425
439
  @workflow()
426
440
  async def dashboard_data(user_id: str):
427
441
  # Fetch all data in parallel
428
442
  user, orders, recommendations = await asyncio.gather(
429
- fetch_user(user_id),
430
- fetch_orders(user_id),
431
- fetch_recommendations(user_id)
443
+ fetch_user(user_id), fetch_orders(user_id), fetch_recommendations(user_id)
432
444
  )
433
445
 
434
- return {
435
- "user": user,
436
- "orders": orders,
437
- "recommendations": recommendations
438
- }
446
+ return {"user": user, "orders": orders, "recommendations": recommendations}
439
447
  ```
440
448
 
441
449
  ### Error Handling
@@ -445,6 +453,7 @@ PyWorkflow distinguishes between retriable and fatal errors:
445
453
  ```python
446
454
  from pyworkflow import FatalError, RetryableError, step
447
455
 
456
+
448
457
  @step(max_retries=3, retry_delay="exponential")
449
458
  async def process_payment(amount: float):
450
459
  try:
@@ -474,14 +483,15 @@ Workflows automatically recover from worker crashes:
474
483
  ```python
475
484
  from pyworkflow import workflow, step, sleep
476
485
 
486
+
477
487
  @workflow(
478
- recover_on_worker_loss=True, # Enable recovery (default for durable)
479
- max_recovery_attempts=5, # Max recovery attempts
488
+ recover_on_worker_loss=True, # Enable recovery (default for durable)
489
+ max_recovery_attempts=5, # Max recovery attempts
480
490
  )
481
491
  async def resilient_workflow(data_id: str):
482
- data = await fetch_data(data_id) # Completed steps are skipped on recovery
483
- await sleep("10m") # Sleep state is preserved
484
- return await process_data(data) # Continues from here after crash
492
+ data = await fetch_data(data_id) # Completed steps are skipped on recovery
493
+ await sleep("10m") # Sleep state is preserved
494
+ return await process_data(data) # Continues from here after crash
485
495
  ```
486
496
 
487
497
  **What happens on worker crash:**
@@ -516,18 +526,10 @@ Prevent duplicate workflow executions with idempotency keys:
516
526
  from pyworkflow import start
517
527
 
518
528
  # Same idempotency key = same workflow
519
- run_id_1 = start(
520
- process_order,
521
- order_id="ORD-123",
522
- idempotency_key="order-ORD-123"
523
- )
529
+ run_id_1 = start(process_order, order_id="ORD-123", idempotency_key="order-ORD-123")
524
530
 
525
531
  # This will return the same run_id, not start a new workflow
526
- run_id_2 = start(
527
- process_order,
528
- order_id="ORD-123",
529
- idempotency_key="order-ORD-123"
530
- )
532
+ run_id_2 = start(process_order, order_id="ORD-123", idempotency_key="order-ORD-123")
531
533
 
532
534
  assert run_id_1 == run_id_2 # True!
533
535
  ```
@@ -544,7 +546,7 @@ configure_logging(
544
546
  level="INFO",
545
547
  log_file="workflow.log",
546
548
  json_logs=True, # JSON format for production
547
- show_context=True # Include run_id, step_id, etc.
549
+ show_context=True, # Include run_id, step_id, etc.
548
550
  )
549
551
 
550
552
  # Logs automatically include:
@@ -565,15 +567,18 @@ import pytest
565
567
  from pyworkflow import workflow, step, start, configure, reset_config
566
568
  from pyworkflow.storage.memory import InMemoryStorageBackend
567
569
 
570
+
568
571
  @step()
569
572
  async def my_step(x: int):
570
573
  return x * 2
571
574
 
575
+
572
576
  @workflow()
573
577
  async def my_workflow(x: int):
574
578
  result = await my_step(x)
575
579
  return result + 1
576
580
 
581
+
577
582
  @pytest.fixture(autouse=True)
578
583
  def setup_storage():
579
584
  reset_config()
@@ -582,6 +587,7 @@ def setup_storage():
582
587
  yield storage
583
588
  reset_config()
584
589
 
590
+
585
591
  @pytest.mark.asyncio
586
592
  async def test_my_workflow(setup_storage):
587
593
  storage = setup_storage
@@ -96,17 +96,20 @@ See [DISTRIBUTED.md](DISTRIBUTED.md) for complete deployment guide.
96
96
  ```python
97
97
  from pyworkflow import workflow, step, start, sleep
98
98
 
99
+
99
100
  @step()
100
101
  async def send_welcome_email(user_id: str):
101
102
  # This runs on any available Celery worker
102
103
  print(f"Sending welcome email to user {user_id}")
103
104
  return f"Email sent to {user_id}"
104
105
 
106
+
105
107
  @step()
106
108
  async def send_tips_email(user_id: str):
107
109
  print(f"Sending tips email to user {user_id}")
108
110
  return f"Tips sent to {user_id}"
109
111
 
112
+
110
113
  @workflow()
111
114
  async def onboarding_workflow(user_id: str):
112
115
  # Send welcome email immediately
@@ -120,6 +123,7 @@ async def onboarding_workflow(user_id: str):
120
123
 
121
124
  return "Onboarding complete"
122
125
 
126
+
123
127
  # Start workflow - executes across Celery workers
124
128
  run_id = start(onboarding_workflow, user_id="user_123")
125
129
  print(f"Workflow started: {run_id}")
@@ -145,6 +149,7 @@ Workflows are the top-level orchestration functions. They coordinate steps, hand
145
149
  ```python
146
150
  from pyworkflow import workflow, start
147
151
 
152
+
148
153
  @workflow(name="process_order", max_duration="1h")
149
154
  async def process_order(order_id: str):
150
155
  """
@@ -163,6 +168,7 @@ async def process_order(order_id: str):
163
168
 
164
169
  return {"order_id": order_id, "status": "completed"}
165
170
 
171
+
166
172
  # Start the workflow
167
173
  run_id = start(process_order, order_id="ORD-123")
168
174
  ```
@@ -174,6 +180,7 @@ Steps are the building blocks of workflows. Each step is an isolated, retryable
174
180
  ```python
175
181
  from pyworkflow import step, RetryableError, FatalError
176
182
 
183
+
177
184
  @step(max_retries=5, retry_delay="exponential")
178
185
  async def call_external_api(url: str):
179
186
  """
@@ -205,11 +212,13 @@ By default, steps in a Celery runtime are dispatched to worker processes via the
205
212
  ```python
206
213
  from pyworkflow import step
207
214
 
215
+
208
216
  @step(force_local=True)
209
217
  async def quick_transform(data: dict):
210
218
  """Runs inline even when runtime is Celery."""
211
219
  return {k: v.upper() for k, v in data.items()}
212
220
 
221
+
213
222
  @step()
214
223
  async def heavy_computation(data: dict):
215
224
  """Dispatched to a Celery worker as usual."""
@@ -235,6 +244,7 @@ Workflows can sleep for any duration. During sleep, the workflow suspends and co
235
244
  ```python
236
245
  from pyworkflow import workflow, sleep
237
246
 
247
+
238
248
  @workflow()
239
249
  async def scheduled_reminder(user_id: str):
240
250
  # Send immediate reminder
@@ -331,35 +341,33 @@ Use Python's native `asyncio.gather()` for parallel step execution:
331
341
  import asyncio
332
342
  from pyworkflow import workflow, step
333
343
 
344
+
334
345
  @step()
335
346
  async def fetch_user(user_id: str):
336
347
  # Fetch user data
337
348
  return {"id": user_id, "name": "Alice"}
338
349
 
350
+
339
351
  @step()
340
352
  async def fetch_orders(user_id: str):
341
353
  # Fetch user orders
342
354
  return [{"id": "ORD-1"}, {"id": "ORD-2"}]
343
355
 
356
+
344
357
  @step()
345
358
  async def fetch_recommendations(user_id: str):
346
359
  # Fetch recommendations
347
360
  return ["Product A", "Product B"]
348
361
 
362
+
349
363
  @workflow()
350
364
  async def dashboard_data(user_id: str):
351
365
  # Fetch all data in parallel
352
366
  user, orders, recommendations = await asyncio.gather(
353
- fetch_user(user_id),
354
- fetch_orders(user_id),
355
- fetch_recommendations(user_id)
367
+ fetch_user(user_id), fetch_orders(user_id), fetch_recommendations(user_id)
356
368
  )
357
369
 
358
- return {
359
- "user": user,
360
- "orders": orders,
361
- "recommendations": recommendations
362
- }
370
+ return {"user": user, "orders": orders, "recommendations": recommendations}
363
371
  ```
364
372
 
365
373
  ### Error Handling
@@ -369,6 +377,7 @@ PyWorkflow distinguishes between retriable and fatal errors:
369
377
  ```python
370
378
  from pyworkflow import FatalError, RetryableError, step
371
379
 
380
+
372
381
  @step(max_retries=3, retry_delay="exponential")
373
382
  async def process_payment(amount: float):
374
383
  try:
@@ -398,14 +407,15 @@ Workflows automatically recover from worker crashes:
398
407
  ```python
399
408
  from pyworkflow import workflow, step, sleep
400
409
 
410
+
401
411
  @workflow(
402
- recover_on_worker_loss=True, # Enable recovery (default for durable)
403
- max_recovery_attempts=5, # Max recovery attempts
412
+ recover_on_worker_loss=True, # Enable recovery (default for durable)
413
+ max_recovery_attempts=5, # Max recovery attempts
404
414
  )
405
415
  async def resilient_workflow(data_id: str):
406
- data = await fetch_data(data_id) # Completed steps are skipped on recovery
407
- await sleep("10m") # Sleep state is preserved
408
- return await process_data(data) # Continues from here after crash
416
+ data = await fetch_data(data_id) # Completed steps are skipped on recovery
417
+ await sleep("10m") # Sleep state is preserved
418
+ return await process_data(data) # Continues from here after crash
409
419
  ```
410
420
 
411
421
  **What happens on worker crash:**
@@ -440,18 +450,10 @@ Prevent duplicate workflow executions with idempotency keys:
440
450
  from pyworkflow import start
441
451
 
442
452
  # Same idempotency key = same workflow
443
- run_id_1 = start(
444
- process_order,
445
- order_id="ORD-123",
446
- idempotency_key="order-ORD-123"
447
- )
453
+ run_id_1 = start(process_order, order_id="ORD-123", idempotency_key="order-ORD-123")
448
454
 
449
455
  # This will return the same run_id, not start a new workflow
450
- run_id_2 = start(
451
- process_order,
452
- order_id="ORD-123",
453
- idempotency_key="order-ORD-123"
454
- )
456
+ run_id_2 = start(process_order, order_id="ORD-123", idempotency_key="order-ORD-123")
455
457
 
456
458
  assert run_id_1 == run_id_2 # True!
457
459
  ```
@@ -468,7 +470,7 @@ configure_logging(
468
470
  level="INFO",
469
471
  log_file="workflow.log",
470
472
  json_logs=True, # JSON format for production
471
- show_context=True # Include run_id, step_id, etc.
473
+ show_context=True, # Include run_id, step_id, etc.
472
474
  )
473
475
 
474
476
  # Logs automatically include:
@@ -489,15 +491,18 @@ import pytest
489
491
  from pyworkflow import workflow, step, start, configure, reset_config
490
492
  from pyworkflow.storage.memory import InMemoryStorageBackend
491
493
 
494
+
492
495
  @step()
493
496
  async def my_step(x: int):
494
497
  return x * 2
495
498
 
499
+
496
500
  @workflow()
497
501
  async def my_workflow(x: int):
498
502
  result = await my_step(x)
499
503
  return result + 1
500
504
 
505
+
501
506
  @pytest.fixture(autouse=True)
502
507
  def setup_storage():
503
508
  reset_config()
@@ -506,6 +511,7 @@ def setup_storage():
506
511
  yield storage
507
512
  reset_config()
508
513
 
514
+
509
515
  @pytest.mark.asyncio
510
516
  async def test_my_workflow(setup_storage):
511
517
  storage = setup_storage