pyworkflow-engine 0.1.7__py3-none-any.whl

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 (196) hide show
  1. dashboard/backend/app/__init__.py +1 -0
  2. dashboard/backend/app/config.py +32 -0
  3. dashboard/backend/app/controllers/__init__.py +6 -0
  4. dashboard/backend/app/controllers/run_controller.py +86 -0
  5. dashboard/backend/app/controllers/workflow_controller.py +33 -0
  6. dashboard/backend/app/dependencies/__init__.py +5 -0
  7. dashboard/backend/app/dependencies/storage.py +50 -0
  8. dashboard/backend/app/repositories/__init__.py +6 -0
  9. dashboard/backend/app/repositories/run_repository.py +80 -0
  10. dashboard/backend/app/repositories/workflow_repository.py +27 -0
  11. dashboard/backend/app/rest/__init__.py +8 -0
  12. dashboard/backend/app/rest/v1/__init__.py +12 -0
  13. dashboard/backend/app/rest/v1/health.py +33 -0
  14. dashboard/backend/app/rest/v1/runs.py +133 -0
  15. dashboard/backend/app/rest/v1/workflows.py +41 -0
  16. dashboard/backend/app/schemas/__init__.py +23 -0
  17. dashboard/backend/app/schemas/common.py +16 -0
  18. dashboard/backend/app/schemas/event.py +24 -0
  19. dashboard/backend/app/schemas/hook.py +25 -0
  20. dashboard/backend/app/schemas/run.py +54 -0
  21. dashboard/backend/app/schemas/step.py +28 -0
  22. dashboard/backend/app/schemas/workflow.py +31 -0
  23. dashboard/backend/app/server.py +87 -0
  24. dashboard/backend/app/services/__init__.py +6 -0
  25. dashboard/backend/app/services/run_service.py +240 -0
  26. dashboard/backend/app/services/workflow_service.py +155 -0
  27. dashboard/backend/main.py +18 -0
  28. docs/concepts/cancellation.mdx +362 -0
  29. docs/concepts/continue-as-new.mdx +434 -0
  30. docs/concepts/events.mdx +266 -0
  31. docs/concepts/fault-tolerance.mdx +370 -0
  32. docs/concepts/hooks.mdx +552 -0
  33. docs/concepts/limitations.mdx +167 -0
  34. docs/concepts/schedules.mdx +775 -0
  35. docs/concepts/sleep.mdx +312 -0
  36. docs/concepts/steps.mdx +301 -0
  37. docs/concepts/workflows.mdx +255 -0
  38. docs/guides/cli.mdx +942 -0
  39. docs/guides/configuration.mdx +560 -0
  40. docs/introduction.mdx +155 -0
  41. docs/quickstart.mdx +279 -0
  42. examples/__init__.py +1 -0
  43. examples/celery/__init__.py +1 -0
  44. examples/celery/durable/docker-compose.yml +55 -0
  45. examples/celery/durable/pyworkflow.config.yaml +12 -0
  46. examples/celery/durable/workflows/__init__.py +122 -0
  47. examples/celery/durable/workflows/basic.py +87 -0
  48. examples/celery/durable/workflows/batch_processing.py +102 -0
  49. examples/celery/durable/workflows/cancellation.py +273 -0
  50. examples/celery/durable/workflows/child_workflow_patterns.py +240 -0
  51. examples/celery/durable/workflows/child_workflows.py +202 -0
  52. examples/celery/durable/workflows/continue_as_new.py +260 -0
  53. examples/celery/durable/workflows/fault_tolerance.py +210 -0
  54. examples/celery/durable/workflows/hooks.py +211 -0
  55. examples/celery/durable/workflows/idempotency.py +112 -0
  56. examples/celery/durable/workflows/long_running.py +99 -0
  57. examples/celery/durable/workflows/retries.py +101 -0
  58. examples/celery/durable/workflows/schedules.py +209 -0
  59. examples/celery/transient/01_basic_workflow.py +91 -0
  60. examples/celery/transient/02_fault_tolerance.py +257 -0
  61. examples/celery/transient/__init__.py +20 -0
  62. examples/celery/transient/pyworkflow.config.yaml +25 -0
  63. examples/local/__init__.py +1 -0
  64. examples/local/durable/01_basic_workflow.py +94 -0
  65. examples/local/durable/02_file_storage.py +132 -0
  66. examples/local/durable/03_retries.py +169 -0
  67. examples/local/durable/04_long_running.py +119 -0
  68. examples/local/durable/05_event_log.py +145 -0
  69. examples/local/durable/06_idempotency.py +148 -0
  70. examples/local/durable/07_hooks.py +334 -0
  71. examples/local/durable/08_cancellation.py +233 -0
  72. examples/local/durable/09_child_workflows.py +198 -0
  73. examples/local/durable/10_child_workflow_patterns.py +265 -0
  74. examples/local/durable/11_continue_as_new.py +249 -0
  75. examples/local/durable/12_schedules.py +198 -0
  76. examples/local/durable/__init__.py +1 -0
  77. examples/local/transient/01_quick_tasks.py +87 -0
  78. examples/local/transient/02_retries.py +130 -0
  79. examples/local/transient/03_sleep.py +141 -0
  80. examples/local/transient/__init__.py +1 -0
  81. pyworkflow/__init__.py +256 -0
  82. pyworkflow/aws/__init__.py +68 -0
  83. pyworkflow/aws/context.py +234 -0
  84. pyworkflow/aws/handler.py +184 -0
  85. pyworkflow/aws/testing.py +310 -0
  86. pyworkflow/celery/__init__.py +41 -0
  87. pyworkflow/celery/app.py +198 -0
  88. pyworkflow/celery/scheduler.py +315 -0
  89. pyworkflow/celery/tasks.py +1746 -0
  90. pyworkflow/cli/__init__.py +132 -0
  91. pyworkflow/cli/__main__.py +6 -0
  92. pyworkflow/cli/commands/__init__.py +1 -0
  93. pyworkflow/cli/commands/hooks.py +640 -0
  94. pyworkflow/cli/commands/quickstart.py +495 -0
  95. pyworkflow/cli/commands/runs.py +773 -0
  96. pyworkflow/cli/commands/scheduler.py +130 -0
  97. pyworkflow/cli/commands/schedules.py +794 -0
  98. pyworkflow/cli/commands/setup.py +703 -0
  99. pyworkflow/cli/commands/worker.py +413 -0
  100. pyworkflow/cli/commands/workflows.py +1257 -0
  101. pyworkflow/cli/output/__init__.py +1 -0
  102. pyworkflow/cli/output/formatters.py +321 -0
  103. pyworkflow/cli/output/styles.py +121 -0
  104. pyworkflow/cli/utils/__init__.py +1 -0
  105. pyworkflow/cli/utils/async_helpers.py +30 -0
  106. pyworkflow/cli/utils/config.py +130 -0
  107. pyworkflow/cli/utils/config_generator.py +344 -0
  108. pyworkflow/cli/utils/discovery.py +53 -0
  109. pyworkflow/cli/utils/docker_manager.py +651 -0
  110. pyworkflow/cli/utils/interactive.py +364 -0
  111. pyworkflow/cli/utils/storage.py +115 -0
  112. pyworkflow/config.py +329 -0
  113. pyworkflow/context/__init__.py +63 -0
  114. pyworkflow/context/aws.py +230 -0
  115. pyworkflow/context/base.py +416 -0
  116. pyworkflow/context/local.py +930 -0
  117. pyworkflow/context/mock.py +381 -0
  118. pyworkflow/core/__init__.py +0 -0
  119. pyworkflow/core/exceptions.py +353 -0
  120. pyworkflow/core/registry.py +313 -0
  121. pyworkflow/core/scheduled.py +328 -0
  122. pyworkflow/core/step.py +494 -0
  123. pyworkflow/core/workflow.py +294 -0
  124. pyworkflow/discovery.py +248 -0
  125. pyworkflow/engine/__init__.py +0 -0
  126. pyworkflow/engine/events.py +879 -0
  127. pyworkflow/engine/executor.py +682 -0
  128. pyworkflow/engine/replay.py +273 -0
  129. pyworkflow/observability/__init__.py +19 -0
  130. pyworkflow/observability/logging.py +234 -0
  131. pyworkflow/primitives/__init__.py +33 -0
  132. pyworkflow/primitives/child_handle.py +174 -0
  133. pyworkflow/primitives/child_workflow.py +372 -0
  134. pyworkflow/primitives/continue_as_new.py +101 -0
  135. pyworkflow/primitives/define_hook.py +150 -0
  136. pyworkflow/primitives/hooks.py +97 -0
  137. pyworkflow/primitives/resume_hook.py +210 -0
  138. pyworkflow/primitives/schedule.py +545 -0
  139. pyworkflow/primitives/shield.py +96 -0
  140. pyworkflow/primitives/sleep.py +100 -0
  141. pyworkflow/runtime/__init__.py +21 -0
  142. pyworkflow/runtime/base.py +179 -0
  143. pyworkflow/runtime/celery.py +310 -0
  144. pyworkflow/runtime/factory.py +101 -0
  145. pyworkflow/runtime/local.py +706 -0
  146. pyworkflow/scheduler/__init__.py +9 -0
  147. pyworkflow/scheduler/local.py +248 -0
  148. pyworkflow/serialization/__init__.py +0 -0
  149. pyworkflow/serialization/decoder.py +146 -0
  150. pyworkflow/serialization/encoder.py +162 -0
  151. pyworkflow/storage/__init__.py +54 -0
  152. pyworkflow/storage/base.py +612 -0
  153. pyworkflow/storage/config.py +185 -0
  154. pyworkflow/storage/dynamodb.py +1315 -0
  155. pyworkflow/storage/file.py +827 -0
  156. pyworkflow/storage/memory.py +549 -0
  157. pyworkflow/storage/postgres.py +1161 -0
  158. pyworkflow/storage/schemas.py +486 -0
  159. pyworkflow/storage/sqlite.py +1136 -0
  160. pyworkflow/utils/__init__.py +0 -0
  161. pyworkflow/utils/duration.py +177 -0
  162. pyworkflow/utils/schedule.py +391 -0
  163. pyworkflow_engine-0.1.7.dist-info/METADATA +687 -0
  164. pyworkflow_engine-0.1.7.dist-info/RECORD +196 -0
  165. pyworkflow_engine-0.1.7.dist-info/WHEEL +5 -0
  166. pyworkflow_engine-0.1.7.dist-info/entry_points.txt +2 -0
  167. pyworkflow_engine-0.1.7.dist-info/licenses/LICENSE +21 -0
  168. pyworkflow_engine-0.1.7.dist-info/top_level.txt +5 -0
  169. tests/examples/__init__.py +0 -0
  170. tests/integration/__init__.py +0 -0
  171. tests/integration/test_cancellation.py +330 -0
  172. tests/integration/test_child_workflows.py +439 -0
  173. tests/integration/test_continue_as_new.py +428 -0
  174. tests/integration/test_dynamodb_storage.py +1146 -0
  175. tests/integration/test_fault_tolerance.py +369 -0
  176. tests/integration/test_schedule_storage.py +484 -0
  177. tests/unit/__init__.py +0 -0
  178. tests/unit/backends/__init__.py +1 -0
  179. tests/unit/backends/test_dynamodb_storage.py +1554 -0
  180. tests/unit/backends/test_postgres_storage.py +1281 -0
  181. tests/unit/backends/test_sqlite_storage.py +1460 -0
  182. tests/unit/conftest.py +41 -0
  183. tests/unit/test_cancellation.py +364 -0
  184. tests/unit/test_child_workflows.py +680 -0
  185. tests/unit/test_continue_as_new.py +441 -0
  186. tests/unit/test_event_limits.py +316 -0
  187. tests/unit/test_executor.py +320 -0
  188. tests/unit/test_fault_tolerance.py +334 -0
  189. tests/unit/test_hooks.py +495 -0
  190. tests/unit/test_registry.py +261 -0
  191. tests/unit/test_replay.py +420 -0
  192. tests/unit/test_schedule_schemas.py +285 -0
  193. tests/unit/test_schedule_utils.py +286 -0
  194. tests/unit/test_scheduled_workflow.py +274 -0
  195. tests/unit/test_step.py +353 -0
  196. tests/unit/test_workflow.py +243 -0
@@ -0,0 +1,196 @@
1
+ dashboard/backend/main.py,sha256=0w7mg8gHbJJPlbD-JQPMJxLsswIvZRW8lNDnjBhY8R4,338
2
+ dashboard/backend/app/__init__.py,sha256=VhJu_FV9ke7EZlC3VowVxOmVrcPWPGU2AfMkRgLwEPA,36
3
+ dashboard/backend/app/config.py,sha256=SofNanrybmZhgtF0wFT6WZeJrbz9ackwK0XvdjOxglY,814
4
+ dashboard/backend/app/server.py,sha256=Vp9y5Eu1ktN9ZDVgFYfRWcVz7QdJmJhVmgfQocldOWI,2405
5
+ dashboard/backend/app/controllers/__init__.py,sha256=EUqGJEODJkug1YFHzEgSJTJftIWzzVvXnHZpML7jdYc,201
6
+ dashboard/backend/app/controllers/run_controller.py,sha256=HeSw4K3D2txkJFjU5afLnrOg53uWBoLW5aUHPBQdB5w,2716
7
+ dashboard/backend/app/controllers/workflow_controller.py,sha256=q0_IzWKsC-qpa7MzF_brO01xjNKzGPKKY1oXut8GQiw,1024
8
+ dashboard/backend/app/dependencies/__init__.py,sha256=Bn-fjVgsjNf_Tcai8PyKhwDs3yT86CwmN7fB7VNSiNs,105
9
+ dashboard/backend/app/dependencies/storage.py,sha256=rBeHnGQa49vn95peNIouaVHGaGOrsaIAfXEwkqCslR0,1703
10
+ dashboard/backend/app/repositories/__init__.py,sha256=T-esMlJimz-Mu1ZhHcPmZI7Mil6n0Hl-HSos_PEIWr4,202
11
+ dashboard/backend/app/repositories/run_repository.py,sha256=GhjCGjBdKxYCeA9zJZcZRJea-1PBoLZ3iyFPvEQEkzU,2357
12
+ dashboard/backend/app/repositories/workflow_repository.py,sha256=LMSm28-_iWFedQQvzsnGwQGwAj0RXj29v-ZQQEQsqkc,752
13
+ dashboard/backend/app/rest/__init__.py,sha256=VruucqKlRXLgK9eoa5M_DoPRJzZhHUY6-3MiTixAwME,172
14
+ dashboard/backend/app/rest/v1/__init__.py,sha256=GCUz23b_z8vqLiU-9M3qYlZ5axv6_nesInPdGGJrkRk,443
15
+ dashboard/backend/app/rest/v1/health.py,sha256=Zmt0CA5PJcIO_hHPFuM15zrnzZroFdtg4uToleWpY1c,773
16
+ dashboard/backend/app/rest/v1/runs.py,sha256=E4XVQYjzSqBTT44gmOA6Io-q7cta465MTwbqv4S3lac,4146
17
+ dashboard/backend/app/rest/v1/workflows.py,sha256=ygfM6yettagC-nrJuHo1JGrouGhXFg8saa1-HULpCF4,1078
18
+ dashboard/backend/app/schemas/__init__.py,sha256=12Dxel8goPL1-lBRXIq7ieIKVTvUZwlfEAFaZrC7UzY,730
19
+ dashboard/backend/app/schemas/common.py,sha256=lIzI4CK4w0YssZG2zEP90fLNa6autOWilAAELFoQCaU,281
20
+ dashboard/backend/app/schemas/event.py,sha256=Yb_wPbFNV4njP2SodCnYOscBhUhkUrASzVm7B6a4EB4,462
21
+ dashboard/backend/app/schemas/hook.py,sha256=Fo-NLU35JC7rJjgT_la-lR13Sz2aOHddlfUFlBk286U,501
22
+ dashboard/backend/app/schemas/run.py,sha256=BfnRSDDS3jnSLX5g6EJvPNP4tT4UQw4_YP38v2OeaZw,1228
23
+ dashboard/backend/app/schemas/step.py,sha256=nL9CJtl5nQ3M1xZt4LwlzPkf5j4VKVDzOW3V4GC1-74,600
24
+ dashboard/backend/app/schemas/workflow.py,sha256=I-ULGItjMPSqtxqQ1mcRmmb4dVSMebKHUTT5Vh9by70,708
25
+ dashboard/backend/app/services/__init__.py,sha256=CiywPcgYKoHfOqqGolfE-LrlZ64Wxa2mpBWoFQQLJRo,174
26
+ dashboard/backend/app/services/run_service.py,sha256=WOpp9AzrJdMRWx9NYqYp2LhcL5y3X8-_KLOYeq8fUa0,7178
27
+ dashboard/backend/app/services/workflow_service.py,sha256=A0-4-GA7nVnBqolISHblboy6UuxMDeaBb9fjILBAPCo,4287
28
+ docs/introduction.mdx,sha256=S_J9FZcVMwBWn5gehphz-6DhYMcRleAtXySpjA9m8BI,5613
29
+ docs/quickstart.mdx,sha256=_MYZu3qxdBc1B_Hn7gebCNj6Z0HWANb1mKmDPb0l3DU,7464
30
+ docs/concepts/cancellation.mdx,sha256=AaBfXHgNlrXmvDpyZEppgKTu0D4yAp0TG5--hDh0G9E,11568
31
+ docs/concepts/continue-as-new.mdx,sha256=F6xBMwXLk5j8doLwIeL7aoS-GjL2PwALnYBVg8jE_gY,13599
32
+ docs/concepts/events.mdx,sha256=v4qyp3vu5MVEs7n17Q1Wz4kyPPY-wFfKe0apagEFCWs,7868
33
+ docs/concepts/fault-tolerance.mdx,sha256=I2YmY7wcDFST1V4jdBX-Kib0LBJbliORIztYdzkAQwA,14054
34
+ docs/concepts/hooks.mdx,sha256=prXaPOGjdKWcOwjKbAFpcjJwUk6qBNuY5L0cu4JBzlk,15393
35
+ docs/concepts/limitations.mdx,sha256=-RcgqGtc2KdtORKWTk7LLmXihvP1uGEMyMH5lvXBBcc,5422
36
+ docs/concepts/schedules.mdx,sha256=aM6UVyQOQ4cMoXH7nGJA_5k2o_Yo7qxN6dstlix2Ro4,21897
37
+ docs/concepts/sleep.mdx,sha256=pxB6P-Ruvp6hgyQiGVzijmkhvzMWqz2ROMOhdFynVc4,7430
38
+ docs/concepts/steps.mdx,sha256=Q933pi1W8zln8EU50Ozk98C97Pc1gKTfKFuElUju31Q,8091
39
+ docs/concepts/workflows.mdx,sha256=M8MD0tU-hnGhnpdvGCrGwNbfwpJJjA2rh9M2KNgiolg,8010
40
+ docs/guides/cli.mdx,sha256=vRhdght9ZoQZ48YqIPb_8d6ABmv2x1lSnJ65cLd9Sak,24072
41
+ docs/guides/configuration.mdx,sha256=Qs1yO96HOl8WJism6-0Rqz48XW4Gd6wfVqKaARhYF68,15044
42
+ examples/__init__.py,sha256=ZKOmZiyKXe_C014WCgNlcrEHb2FweH2Obl5_jy4tFtI,30
43
+ examples/celery/__init__.py,sha256=_WVdfeZVa-byrFJtEm3ox6kTv4MwJ8Q2XIv8VF5kIx4,37
44
+ examples/celery/durable/docker-compose.yml,sha256=6clHtaRi974en3ELhZBLcWZnQDd9UivLXuXjNuEfV50,1528
45
+ examples/celery/durable/pyworkflow.config.yaml,sha256=s5RQCQTaTnHKwGYhWs7qvnnQxhH0UcmLqY1rlUCAb4g,293
46
+ examples/celery/durable/workflows/__init__.py,sha256=sEy7aJnx5rLPRdbGs5iIHFaSBkvp6Y7OUPBSM7r5sxQ,3287
47
+ examples/celery/durable/workflows/basic.py,sha256=2CrQ31Y59rWtgeprPG0j1-hH7FfkxB3H-QOsP8sEeuY,2796
48
+ examples/celery/durable/workflows/batch_processing.py,sha256=E3Mz9lnrOzb4EaOJ8jarWVqrbz1gxLYtFOE07Fxo5dQ,3371
49
+ examples/celery/durable/workflows/cancellation.py,sha256=EMFr-4aZGW15dCgKtFuLQKPC9eBsxgfkw8XTueLXV34,9117
50
+ examples/celery/durable/workflows/child_workflow_patterns.py,sha256=L-koPTtjM_JQpt4IXt1s-Z_cJV6C0tu6TocRV1MMCtg,8468
51
+ examples/celery/durable/workflows/child_workflows.py,sha256=8ycbf5lpJpACMcm1FiLHPUvd-oKesADvDSg37s9WMfk,7174
52
+ examples/celery/durable/workflows/continue_as_new.py,sha256=Z3Ey9bE8fCRHQvih10aRDpd6VHdx7fSAzjfmYfXzjAU,8028
53
+ examples/celery/durable/workflows/fault_tolerance.py,sha256=CIblbq9T_zsSKYMuEkLOuZCSMCQ5x8KUp0KEy7QwgOQ,6971
54
+ examples/celery/durable/workflows/hooks.py,sha256=3kY276H-vlBxK04zLCKl9BMVTPCVkSutszGbhLlDHB4,6851
55
+ examples/celery/durable/workflows/idempotency.py,sha256=3LHGvrL7Jw8dPDPktTWYAiy407lpzFeJRRE3DK65sf4,3984
56
+ examples/celery/durable/workflows/long_running.py,sha256=V5t-_fO3GXH3xsflLWruOc4NymJ-nSQanFxUBh6lWBc,3177
57
+ examples/celery/durable/workflows/retries.py,sha256=3-zJnF3mvcHrkaUO8afkUOOFroO1iCY8jWjQP3z-QDQ,3284
58
+ examples/celery/durable/workflows/schedules.py,sha256=U2Ni8rRFt-3Oau3ek5pzsTC-IrxhnYupNqOcMteEHJY,6672
59
+ examples/celery/transient/01_basic_workflow.py,sha256=WuRHofa8wvdnzCu2vtx601cKCivhDD8CVfJIM0xPWDg,2774
60
+ examples/celery/transient/02_fault_tolerance.py,sha256=ihoCnuYq2DwIMI88hxY_YM0LPwAO2IBuxphCG2jThbM,9052
61
+ examples/celery/transient/__init__.py,sha256=pFNAED54Oj2PSksFPxjtv2KrgMLnRDobHZ53rZQUNGA,798
62
+ examples/celery/transient/pyworkflow.config.yaml,sha256=uBxlq4NLyFJrFrPU3ZEh6rbOHCHNuuSSxeMA7t9xAfo,685
63
+ examples/local/__init__.py,sha256=ESXk4CALCAUStP3YsGz9cW0h0tw_C7lpfpABM5HGGyQ,36
64
+ examples/local/durable/01_basic_workflow.py,sha256=dXk7YeTlzpp2LEtOewmsww0WAC-J7Org_Xf71OCvLPc,2768
65
+ examples/local/durable/02_file_storage.py,sha256=4V_MoQBqe-nuMlJhQVFOTgPD4Nl1QgMqYpjwbsu8plw,4330
66
+ examples/local/durable/03_retries.py,sha256=Z2fSj3ldMWrLSt2pvg2Uyup9Jv5NiKsCHsL_k_DE_2o,5807
67
+ examples/local/durable/04_long_running.py,sha256=fiYpa5QuwZmZKswiSsHspiCBa_tFko3w2pMDa3wGtfw,3892
68
+ examples/local/durable/05_event_log.py,sha256=V7n_74u_51THG9vgNDf-1Sqd_TWC6jiszFxx4w5gUx4,4467
69
+ examples/local/durable/06_idempotency.py,sha256=6DOT2ws6-Kf-95mGIPU5OVkugoGON9idyFDYX0sIUuc,5047
70
+ examples/local/durable/07_hooks.py,sha256=yPOfz1npQ_u7FHkU1LjZCO82kd6xihUOeau2nzWoyZo,10432
71
+ examples/local/durable/08_cancellation.py,sha256=bDaZsgnfOfHed7zaY45IUx38Dl9uyta6nm72nxpDGns,7423
72
+ examples/local/durable/09_child_workflows.py,sha256=uAH26iBxMsKBcFvn5zVZO2uiuDXK8rMXffiPJNnz8FY,6731
73
+ examples/local/durable/10_child_workflow_patterns.py,sha256=tyK0QAJLQJe53jdPgH7ay7GWA0WfqJoav_oRIL8KJGA,8476
74
+ examples/local/durable/11_continue_as_new.py,sha256=AHTwKWit1vR1d2zk-o3xZBLBbzX4B7oz_93BNuG6E_s,7747
75
+ examples/local/durable/12_schedules.py,sha256=0zzHhUvhmVrqsBd_4bsewWvlUOnVIeQFx9meX11auQA,6219
76
+ examples/local/durable/__init__.py,sha256=AnkHdfpABaF0GImcTV0Go9ygGGY00HDgDcl_so7xPIY,44
77
+ examples/local/transient/01_quick_tasks.py,sha256=BzbSy69MvnQeyOtYlweqF-nZrIUFDxVJwfDOVcZjl74,2516
78
+ examples/local/transient/02_retries.py,sha256=cxgnnZTNYYEv0dnriyzWa_ClqIiQUutsnj2AIVrZsN4,3976
79
+ examples/local/transient/03_sleep.py,sha256=j7MXbiJ20OZcHPydITx6BtZV81cBmYp4wmSP8RsmPws,4233
80
+ examples/local/transient/__init__.py,sha256=JA91ksUYQCZPAL4mSe71LjFnWuk_dr6fV0m8SQMSKjA,46
81
+ pyworkflow/__init__.py,sha256=42YUz_F3w2W8AUjIML5ivo3l_5KVLF_uv2h2ZeIeAVc,6061
82
+ pyworkflow/config.py,sha256=a_UvywIwmPtGy0SzeWYVk3DVCEeUlIRkDrt-4KfTtE8,11019
83
+ pyworkflow/discovery.py,sha256=snW3l4nvY3Nc067TGlwtn_qdzTU9ybN7YPr8FbvY8iM,8066
84
+ pyworkflow/aws/__init__.py,sha256=Ak_xHcR9LTRX-CwcS0XecYmzrXZw4EM3V9aKBBDEmIk,1741
85
+ pyworkflow/aws/context.py,sha256=Vjyjip6U1Emg-WA5TlBaxFhcg15rf9mVJiPfT4VywHc,8217
86
+ pyworkflow/aws/handler.py,sha256=0SnQuIfQVD99QKMCRFPtrsrV_l1LYKFkzPIRx_2UkSI,5849
87
+ pyworkflow/aws/testing.py,sha256=WrRk9wjbycM-UyHFQWNnA83UE9IrYnhfT38WrbxQT2U,8844
88
+ pyworkflow/celery/__init__.py,sha256=FywVyqnT8AYz9cXkr-wel7_-N7dHFsPNASEPMFESf4Q,1179
89
+ pyworkflow/celery/app.py,sha256=EsmRqervXqnJn7Jl76ZDV9OIcNnIb6fRjDeuZEfYJL8,6456
90
+ pyworkflow/celery/scheduler.py,sha256=Ms4rqRpdpMiLM8l4y3DK-Divunj9afYuUaGGoNQe7P4,11288
91
+ pyworkflow/celery/tasks.py,sha256=s4S7VTjtrEdUCM9QaalAqLSe2uJ3B9FAN-HuyYZytaY,56453
92
+ pyworkflow/cli/__init__.py,sha256=OzK8lQ0qaxy2iQ_TjY9n3Mjd-rKjMxJtkFgJTtAlaQA,3787
93
+ pyworkflow/cli/__main__.py,sha256=LxLLS4FEEPXa5rWpLTtKuivn6Xp9pGia-QKGoxt9SS0,148
94
+ pyworkflow/cli/commands/__init__.py,sha256=IXvnTgukALckkO8fTlZhVRq80ojSqpnIIgboAg_-yZU,39
95
+ pyworkflow/cli/commands/hooks.py,sha256=UtTjfuo4qMwkV0UfdvgWLIqhTw69hnHLjg6-KTVZyRY,21139
96
+ pyworkflow/cli/commands/quickstart.py,sha256=4i_eiCLPAkzXpVoDtlEMgU_JtJpmcffKlgb2dIag5aw,14352
97
+ pyworkflow/cli/commands/runs.py,sha256=QeA3e-SV6CfWm3N6Gni2cHRGREdXNVzUMftliNmunsU,25109
98
+ pyworkflow/cli/commands/scheduler.py,sha256=w2iUoJ1CtEtOg_4TWslTHbzEPVsV-YybqWU9jkf38gs,3706
99
+ pyworkflow/cli/commands/schedules.py,sha256=UCKZLTWsiLwCewCEXmqOVQnptvvuIKsWSTXai61RYbM,23466
100
+ pyworkflow/cli/commands/setup.py,sha256=XuUCjSpmkgayIMIgIMtDuITj7QvPQT8ZKXTs_SGhI6k,23878
101
+ pyworkflow/cli/commands/worker.py,sha256=UJ8bQJTXMEk3BoMiivClTCKNt_f-g75jJ5O-khfcfsY,12110
102
+ pyworkflow/cli/commands/workflows.py,sha256=zRBFeqCa4Uo_wwEjgk0SBmkqgcaMznS6ghe1N0ub8Zs,42673
103
+ pyworkflow/cli/output/__init__.py,sha256=5VxKL3mXah5rCKmctxcAKVwp42T47qT1oBK5LFVHHEg,48
104
+ pyworkflow/cli/output/formatters.py,sha256=QzsgPR3cjIbH0723wuG_HzUx9xC7XMA6-NkT2y2lwtM,8785
105
+ pyworkflow/cli/output/styles.py,sha256=WK6GHq_zQGMITGf16U3Vhc4gG4E7YFlE8_d-uBLGbBk,3035
106
+ pyworkflow/cli/utils/__init__.py,sha256=yzEuZNPwj-ts1oR8QBD3pObRmGbL0oullXF-jIve5wo,40
107
+ pyworkflow/cli/utils/async_helpers.py,sha256=B7bPBiUWV9rzHjtXnINEh7AHO57ytQH1xU6TtxnA2dU,786
108
+ pyworkflow/cli/utils/config.py,sha256=Lb75T6-y45v0JiLS-fNNvFU2KDoMIBWyNqmFG_jsEeU,3928
109
+ pyworkflow/cli/utils/config_generator.py,sha256=fid3UIfG5dTvF3d_FF3R5BYEMSjdYfN35pZ_UGru_Lc,10905
110
+ pyworkflow/cli/utils/discovery.py,sha256=v-5FMresm-kYPW-hQbql6dOIu6sqIoW_TddD16B8SKo,1467
111
+ pyworkflow/cli/utils/docker_manager.py,sha256=nCkPcEg9aLLhc7lbKKan58Tg1HLQWwYYuiyLtOhSY-I,18931
112
+ pyworkflow/cli/utils/interactive.py,sha256=S2Ell-rUzzt3V10diGo5XCgiDcYFYSxoXNYkJ5EQ_Yc,9740
113
+ pyworkflow/cli/utils/storage.py,sha256=a5Iu2Xe1_mPgBVYc8B6I63MFfW12ko7wURqcpq3RBPA,4018
114
+ pyworkflow/context/__init__.py,sha256=HJAz4H2nT7BoRTgTmNSZJP7eXK7er0uwMwNFZug5uV4,1825
115
+ pyworkflow/context/aws.py,sha256=MYxrFsRzCgaZ0YQAyE26UOT_ryxuag5DwiDSodclQIg,7571
116
+ pyworkflow/context/base.py,sha256=-Q49kL8Ow6zRre6Xm60PzYymkvDtxR3SHM8PgfLucI4,12693
117
+ pyworkflow/context/local.py,sha256=m1sNxxmmiPS9BliJsgjg669GFQqvd7vudFW_Jj0E3JQ,32995
118
+ pyworkflow/context/mock.py,sha256=TJzQ3P3_ZHm1lCJZJACIFFvz2ydFxz2cT9eEGOQS5I0,12061
119
+ pyworkflow/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
+ pyworkflow/core/exceptions.py,sha256=F2nbXyoed7wlIJMeGfpgsIC8ZyWcYN0iKtOnBA7-xnQ,10719
121
+ pyworkflow/core/registry.py,sha256=vUtEv2cYJaqG3229-pQ2X8CZXYk416ilFn-hhahgros,9056
122
+ pyworkflow/core/scheduled.py,sha256=479A7IvjHiMob7ZrZtfE6VqtypG6DLIGMGhh16jLIWM,10522
123
+ pyworkflow/core/step.py,sha256=zUXC8oIbe4_x4c94pwsUwtGtkNau6vu6t5XfaNqZVo4,17805
124
+ pyworkflow/core/workflow.py,sha256=NQACOCFLb0M5QN_wGJpm6U3dwsEy_Lr4R5G3g91mU5g,9597
125
+ pyworkflow/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
+ pyworkflow/engine/events.py,sha256=enH1mFOUi50OQUOUWc_cWJAUqtnoq47-IbM8cox2I-8,24368
127
+ pyworkflow/engine/executor.py,sha256=5b50m-a4XjrOoIS9hS4Rsgk_N114s5js6b-LuW2L0Jw,20333
128
+ pyworkflow/engine/replay.py,sha256=bmMb4wzPKaZwPOage3Z-g_5DndYNoSmavMZ9sPiFzYI,9386
129
+ pyworkflow/observability/__init__.py,sha256=M_Uc3WdtshQSxLnj3T8D0M7f4zcCuFzVs8e8PKCuXDc,380
130
+ pyworkflow/observability/logging.py,sha256=4b_N4bIHUxlgOzEn5u1uB-ngCWPNDSU7daKAKxkjBUM,7018
131
+ pyworkflow/primitives/__init__.py,sha256=rEahSVLhG3nSxvcRhJeM1LBSBIV7AkcRTnxuMLmZMTM,1041
132
+ pyworkflow/primitives/child_handle.py,sha256=7NcIaNUQdZEoxmk5gQH1CJ6uQzpro3eFo-sEaM6l6w0,5466
133
+ pyworkflow/primitives/child_workflow.py,sha256=-KU9puqd9wnhdkvT6j7mE_7V1QiHNFtMOqiCT77h4PI,13141
134
+ pyworkflow/primitives/continue_as_new.py,sha256=NKcimHsgr5ExkvRvfO28hxgPw_I7Q74Vz9WL8r0PhPc,3329
135
+ pyworkflow/primitives/define_hook.py,sha256=gNzk7DuObfWG1T9AdHnDnGLHNKjnApiVRlCKPObugfY,4443
136
+ pyworkflow/primitives/hooks.py,sha256=ws9U81ymsY8M4FFTvJ2X4EMGmIrilb3vCKZ0V_EGZdE,3085
137
+ pyworkflow/primitives/resume_hook.py,sha256=q6gb0qsAhOkFRKMs-PkbLSFLnLerx0VGMkPp9CbkXZQ,6192
138
+ pyworkflow/primitives/schedule.py,sha256=2hVM2Swl9dRx3RHd5nblJLaU8HaSy-NHYue2Cf9TOcU,14961
139
+ pyworkflow/primitives/shield.py,sha256=MUYakU0euZoYNb6MbFyRfJN8GEXsRFkIbZEo84vRN9c,2924
140
+ pyworkflow/primitives/sleep.py,sha256=iH1e5CoWY-jZbYNAU3GRW1xR_8EtCuPIcIohzU4jWJo,3097
141
+ pyworkflow/runtime/__init__.py,sha256=DkwTgFCMRGyyW8NGcW7Nyy9beOg5kO1TXhqhysj1-aY,649
142
+ pyworkflow/runtime/base.py,sha256=-X2pct03XuA3o1P6yD5ywTDgegN6_a450gG8MBVeKRE,5190
143
+ pyworkflow/runtime/celery.py,sha256=0hSwN4alL69ZgnIgYiITcJ0s_iTi8A_xrsdKo89k4Hs,9431
144
+ pyworkflow/runtime/factory.py,sha256=TRbqWPfyZ0tPFKb0faI9SkBRXxE5AEVTwGW4pS2diM8,2684
145
+ pyworkflow/runtime/local.py,sha256=J3OTbPWtTzD8bMVuFV-0EmJZGI1nI0j9vrwvnmOgG6Q,24129
146
+ pyworkflow/scheduler/__init__.py,sha256=lQQo0Cia_ULIg-KPIrqILV30rUIzybxj1k_ZZTQNZyg,222
147
+ pyworkflow/scheduler/local.py,sha256=CnK4UC6ofD3_AZJUlO9iUAdgAnbMmJvPaL_VucNKs5Q,8154
148
+ pyworkflow/serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
149
+ pyworkflow/serialization/decoder.py,sha256=F7Ofuw1Yzo82iSFFXiK2yoW_v2YRbLMpX3CQbKjm0Ls,3860
150
+ pyworkflow/serialization/encoder.py,sha256=ZBwAxe5Bb4MCfFJePHw7ArJlIbBieSwUgsysGCI2iPU,4108
151
+ pyworkflow/storage/__init__.py,sha256=dzsZbxtAkouWGVbIi6euHyMMYnfAJ6L5v2ze5tI6-vw,1466
152
+ pyworkflow/storage/base.py,sha256=nYub8nwp7G0pD4k-I_FG88G4BA1Sry5sjeEbMiyVhKw,15153
153
+ pyworkflow/storage/config.py,sha256=Tg3uWcc-lYZ2NtFhZLi946aygcQhRCINxo2Yoc011tc,6534
154
+ pyworkflow/storage/dynamodb.py,sha256=GB3vL9TuiIKx1Gt_wmITrTSYncq8_v7YLsK6azc8a70,52202
155
+ pyworkflow/storage/file.py,sha256=wKfFfjQnhQh8VT1fDrBTySxKFukmrNuSHBXTasihZnI,27898
156
+ pyworkflow/storage/memory.py,sha256=beE-H9tROs1TMIzn4otk9BrpAVYtwMB35patRC8D2sM,19178
157
+ pyworkflow/storage/postgres.py,sha256=CtVSwn3tHYy_1aULWymddkJnLLQr3W6t9oVcp-tlvO0,40569
158
+ pyworkflow/storage/schemas.py,sha256=9q0hOOnlilsZ9tVfhHpoLIw1z67P_Gf84mfa27RPq1k,17874
159
+ pyworkflow/storage/sqlite.py,sha256=T85b21tUMag8ARjFVrR3YdO9SB7gp6nURdxJCp91-gQ,38845
160
+ pyworkflow/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
161
+ pyworkflow/utils/duration.py,sha256=C-itmiSQQlplw7j6XB679hLF9xYGnyCwm7twO88OF8U,3978
162
+ pyworkflow/utils/schedule.py,sha256=dO_MkGFyfwZpb0LDlW6BGyZzlPuQIA6dc6j9nk9lc4Y,10691
163
+ pyworkflow_engine-0.1.7.dist-info/licenses/LICENSE,sha256=Y49RCTZ5ayn_yzBcRxnyIFdcMCyuYm150aty_FIznfY,1080
164
+ tests/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
165
+ tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
+ tests/integration/test_cancellation.py,sha256=Y57gJxSbFKa2CEeSCY6eZZ3zUEWKCam63svP6qXct8A,9998
167
+ tests/integration/test_child_workflows.py,sha256=nTtIqAVYgAnb3gKG_1oDoN2JGOILMZgE1rDyMNYEyFY,12243
168
+ tests/integration/test_continue_as_new.py,sha256=D2gkgGyW3ntcdgifb4gC9xyW_h08Su-1c-MAKw1yTtc,13756
169
+ tests/integration/test_dynamodb_storage.py,sha256=X9ZBKG05TaKeVRn86uzzI9diYsupVoSaj4RGDdW-Gj8,38093
170
+ tests/integration/test_fault_tolerance.py,sha256=yR3Nywb06fpA8pVJ-fgkxomWbYoVxGWGlOarc7Pfd9c,12474
171
+ tests/integration/test_schedule_storage.py,sha256=S-6T_x0Xe8H6P5Y54FgRUqw5C4IVT2SjJUCrzhoKqEA,16105
172
+ tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
+ tests/unit/conftest.py,sha256=5bVhDRtL3F9T5JGGL4R1K6xwbryZpR7ttsWt_29eKwY,997
174
+ tests/unit/test_cancellation.py,sha256=HaDpLdnsuBo-keY5FzkU8hvyxKhAlh37_PnoiT02pfI,12285
175
+ tests/unit/test_child_workflows.py,sha256=PMGNKpzdhOJ_Cn5FF-JPMZSthk9BAQTh4ieGFik0Zm0,24449
176
+ tests/unit/test_continue_as_new.py,sha256=uoicJsHTU8vEOUyHHpPwBb2l9iHoAJz_fvyUsbkfPQY,14870
177
+ tests/unit/test_event_limits.py,sha256=DWbj41jqmphcQjhz6vWNB4cm1QHvmExa55YMLyT8qxw,10729
178
+ tests/unit/test_executor.py,sha256=yL3In3LXEIDmMgn6q9OihPQnueyMVXICdhDIQS_BVB4,10685
179
+ tests/unit/test_fault_tolerance.py,sha256=wjKaHwVAEAJt6N0wrt8L3Qh74NZbEW-qhKF5LIOslbU,11120
180
+ tests/unit/test_hooks.py,sha256=nfBr-_FG5l_7LsUpJG-Uup06yNvrrh3NaneW7LdzHIU,16108
181
+ tests/unit/test_registry.py,sha256=Bcui3IFDXNbXyi96FDfyGkjdB21vwV-QX6RlGjeb-3E,7430
182
+ tests/unit/test_replay.py,sha256=2DifrAa_MNI09ZY2lewVvSJcQ-vjditzTJgHFG_JQzI,13086
183
+ tests/unit/test_schedule_schemas.py,sha256=1QS4Qbq_4UrevIJKE5zwWT2Uxlre8pnrzNstMRvAUIk,9607
184
+ tests/unit/test_schedule_utils.py,sha256=vP39cddGT4W_S5o9xMQ472EL47lePzbYqCu4uHh7ojo,9953
185
+ tests/unit/test_scheduled_workflow.py,sha256=YMPhwSavc9I5i7Wv0_H1mew2cPNVQyLik0FMsiq22fI,8632
186
+ tests/unit/test_step.py,sha256=Wr6sxgSSOIaIaiJhZFvMs07dS2ra8Hn3yNHGWOzvi34,11226
187
+ tests/unit/test_workflow.py,sha256=vb_kwSMQIJuf1PLmowCs9YMv6zfdZa_Z2zaWUIf6czc,7663
188
+ tests/unit/backends/__init__.py,sha256=IznqF3Rfz0wDcNHOIREA2JT7RfUXU6lWUiGmNhx1tJc,39
189
+ tests/unit/backends/test_dynamodb_storage.py,sha256=1HAOzxj-izijp54nZUtMH2rlhlhVpEcjZFNIOg17oQc,53890
190
+ tests/unit/backends/test_postgres_storage.py,sha256=NIxiS2TAzqWLeUTl0yZcMbLLzpMI85U6GLqBvyyrMvM,42829
191
+ tests/unit/backends/test_sqlite_storage.py,sha256=Oh3gvpVS8CxSvNUjk68Y2gYUSirUX3tvF4jwidp6NtU,43243
192
+ pyworkflow_engine-0.1.7.dist-info/METADATA,sha256=8pvD6W7ycGQEy10LsdprDg1XZxXMbKRLpwQ5cJRPyuA,19544
193
+ pyworkflow_engine-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
194
+ pyworkflow_engine-0.1.7.dist-info/entry_points.txt,sha256=3IGAfuylnS39U0YX0pxnjrj54kB4iT_bNYrmsiDB-dE,51
195
+ pyworkflow_engine-0.1.7.dist-info/top_level.txt,sha256=klYTMgil7t1g1J7tiSivbSluzH8gIP4_IXtWmRJY07M,41
196
+ pyworkflow_engine-0.1.7.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyworkflow = pyworkflow.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 PyWorkflow Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ dashboard
2
+ docs
3
+ examples
4
+ pyworkflow
5
+ tests
File without changes
File without changes
@@ -0,0 +1,330 @@
1
+ """
2
+ Integration tests for cancellation feature.
3
+
4
+ Tests cover:
5
+ - cancel_workflow() function
6
+ - Cancellation during step execution
7
+ - Cancellation during sleep
8
+ - Cancellation during hook wait
9
+ - Shield prevents cancellation
10
+ - Workflow catches CancellationError for cleanup
11
+ """
12
+
13
+ import asyncio
14
+
15
+ import pytest
16
+
17
+ from pyworkflow import (
18
+ CancellationError,
19
+ RunStatus,
20
+ cancel_workflow,
21
+ shield,
22
+ step,
23
+ )
24
+ from pyworkflow.engine.events import EventType
25
+ from pyworkflow.storage.memory import InMemoryStorageBackend
26
+ from pyworkflow.storage.schemas import WorkflowRun
27
+
28
+
29
+ class TestCancelWorkflowFunction:
30
+ """Test cancel_workflow() function."""
31
+
32
+ @pytest.mark.asyncio
33
+ async def test_cancel_running_workflow(self):
34
+ """Test cancelling a running workflow."""
35
+ storage = InMemoryStorageBackend()
36
+
37
+ # Create a workflow run record
38
+ run = WorkflowRun(
39
+ run_id="run_123",
40
+ workflow_name="test_workflow",
41
+ status=RunStatus.RUNNING,
42
+ )
43
+ await storage.create_run(run)
44
+
45
+ # Cancel the workflow
46
+ result = await cancel_workflow(
47
+ run_id="run_123",
48
+ reason="User cancelled",
49
+ storage=storage,
50
+ )
51
+
52
+ assert result is True
53
+
54
+ # Check cancellation flag was set
55
+ assert await storage.check_cancellation_flag("run_123") is True
56
+
57
+ # Check cancellation event was recorded
58
+ events = await storage.get_events("run_123")
59
+ cancellation_events = [e for e in events if e.type == EventType.CANCELLATION_REQUESTED]
60
+ assert len(cancellation_events) == 1
61
+ assert cancellation_events[0].data["reason"] == "User cancelled"
62
+
63
+ @pytest.mark.asyncio
64
+ async def test_cancel_suspended_workflow(self):
65
+ """Test cancelling a suspended workflow marks it as cancelled."""
66
+ storage = InMemoryStorageBackend()
67
+
68
+ # Create a suspended workflow
69
+ run = WorkflowRun(
70
+ run_id="run_456",
71
+ workflow_name="test_workflow",
72
+ status=RunStatus.SUSPENDED,
73
+ )
74
+ await storage.create_run(run)
75
+
76
+ # Cancel the workflow
77
+ result = await cancel_workflow(
78
+ run_id="run_456",
79
+ storage=storage,
80
+ )
81
+
82
+ assert result is True
83
+
84
+ # Suspended workflows should be marked as cancelled immediately
85
+ updated_run = await storage.get_run("run_456")
86
+ assert updated_run.status == RunStatus.CANCELLED
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_cancel_completed_workflow_returns_false(self):
90
+ """Test cancelling an already completed workflow returns False."""
91
+ storage = InMemoryStorageBackend()
92
+
93
+ # Create a completed workflow
94
+ run = WorkflowRun(
95
+ run_id="run_789",
96
+ workflow_name="test_workflow",
97
+ status=RunStatus.COMPLETED,
98
+ )
99
+ await storage.create_run(run)
100
+
101
+ # Try to cancel
102
+ result = await cancel_workflow(
103
+ run_id="run_789",
104
+ storage=storage,
105
+ )
106
+
107
+ assert result is False
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_cancel_failed_workflow_returns_false(self):
111
+ """Test cancelling an already failed workflow returns False."""
112
+ storage = InMemoryStorageBackend()
113
+
114
+ # Create a failed workflow
115
+ run = WorkflowRun(
116
+ run_id="run_abc",
117
+ workflow_name="test_workflow",
118
+ status=RunStatus.FAILED,
119
+ )
120
+ await storage.create_run(run)
121
+
122
+ # Try to cancel
123
+ result = await cancel_workflow(
124
+ run_id="run_abc",
125
+ storage=storage,
126
+ )
127
+
128
+ assert result is False
129
+
130
+ @pytest.mark.asyncio
131
+ async def test_cancel_already_cancelled_workflow_returns_false(self):
132
+ """Test cancelling an already cancelled workflow returns False."""
133
+ storage = InMemoryStorageBackend()
134
+
135
+ # Create a cancelled workflow
136
+ run = WorkflowRun(
137
+ run_id="run_def",
138
+ workflow_name="test_workflow",
139
+ status=RunStatus.CANCELLED,
140
+ )
141
+ await storage.create_run(run)
142
+
143
+ # Try to cancel again
144
+ result = await cancel_workflow(
145
+ run_id="run_def",
146
+ storage=storage,
147
+ )
148
+
149
+ assert result is False
150
+
151
+ @pytest.mark.asyncio
152
+ async def test_cancel_nonexistent_workflow(self):
153
+ """Test cancelling a non-existent workflow raises error."""
154
+ from pyworkflow import WorkflowNotFoundError
155
+
156
+ storage = InMemoryStorageBackend()
157
+
158
+ with pytest.raises(WorkflowNotFoundError):
159
+ await cancel_workflow(
160
+ run_id="nonexistent",
161
+ storage=storage,
162
+ )
163
+
164
+
165
+ class TestCancellationCheckPoints:
166
+ """Test cancellation at various check points."""
167
+
168
+ @pytest.mark.asyncio
169
+ async def test_step_checks_cancellation(self):
170
+ """Test that step execution checks for cancellation."""
171
+ from pyworkflow.context import LocalContext, reset_context, set_context
172
+
173
+ @step()
174
+ async def my_step():
175
+ return "result"
176
+
177
+ # Create context with cancellation requested
178
+ ctx = LocalContext(
179
+ run_id="test_run",
180
+ workflow_name="test_workflow",
181
+ storage=None,
182
+ durable=False,
183
+ )
184
+ ctx.request_cancellation(reason="Test")
185
+ token = set_context(ctx)
186
+
187
+ try:
188
+ with pytest.raises(CancellationError):
189
+ await my_step()
190
+ finally:
191
+ reset_context(token)
192
+
193
+
194
+ class TestShieldIntegration:
195
+ """Test shield() integration with workflow execution."""
196
+
197
+ @pytest.mark.asyncio
198
+ async def test_shield_allows_cleanup(self):
199
+ """Test shield() allows cleanup operations to complete."""
200
+ from pyworkflow.context import LocalContext, reset_context, set_context
201
+
202
+ cleanup_completed = False
203
+
204
+ async def cleanup():
205
+ nonlocal cleanup_completed
206
+ await asyncio.sleep(0.01) # Simulate cleanup work
207
+ cleanup_completed = True
208
+
209
+ ctx = LocalContext(
210
+ run_id="test_run",
211
+ workflow_name="test_workflow",
212
+ storage=None,
213
+ durable=False,
214
+ )
215
+ ctx.request_cancellation()
216
+ token = set_context(ctx)
217
+
218
+ try:
219
+ async with shield():
220
+ await cleanup()
221
+
222
+ assert cleanup_completed is True
223
+ finally:
224
+ reset_context(token)
225
+
226
+
227
+ class TestFileCancellationFlags:
228
+ """Test file storage backend cancellation flags."""
229
+
230
+ @pytest.mark.asyncio
231
+ async def test_file_storage_cancellation_flags(self, tmp_path):
232
+ """Test FileStorageBackend cancellation flag methods."""
233
+ from pyworkflow.storage.file import FileStorageBackend
234
+
235
+ storage = FileStorageBackend(base_path=str(tmp_path / "workflow_data"))
236
+
237
+ # Initially not set
238
+ assert await storage.check_cancellation_flag("run_123") is False
239
+
240
+ # Set the flag
241
+ await storage.set_cancellation_flag("run_123")
242
+ assert await storage.check_cancellation_flag("run_123") is True
243
+
244
+ # Clear the flag
245
+ await storage.clear_cancellation_flag("run_123")
246
+ assert await storage.check_cancellation_flag("run_123") is False
247
+
248
+ @pytest.mark.asyncio
249
+ async def test_file_storage_clear_nonexistent_flag(self, tmp_path):
250
+ """Test clearing a non-existent flag does not raise."""
251
+ from pyworkflow.storage.file import FileStorageBackend
252
+
253
+ storage = FileStorageBackend(base_path=str(tmp_path / "workflow_data"))
254
+
255
+ # Should not raise
256
+ await storage.clear_cancellation_flag("run_nonexistent")
257
+
258
+
259
+ class TestEventReplayCancellation:
260
+ """Test cancellation state restoration during event replay."""
261
+
262
+ @pytest.mark.asyncio
263
+ async def test_replay_restores_cancellation_state(self):
264
+ """Test that CANCELLATION_REQUESTED event sets context state during replay."""
265
+ from pyworkflow.context import LocalContext
266
+ from pyworkflow.engine.events import create_cancellation_requested_event
267
+
268
+ storage = InMemoryStorageBackend()
269
+
270
+ # Create run
271
+ run = WorkflowRun(
272
+ run_id="run_123",
273
+ workflow_name="test_workflow",
274
+ status=RunStatus.RUNNING,
275
+ )
276
+ await storage.create_run(run)
277
+
278
+ # Record cancellation event
279
+ event = create_cancellation_requested_event(
280
+ run_id="run_123",
281
+ reason="User cancelled",
282
+ )
283
+ await storage.record_event(event)
284
+
285
+ # Get events
286
+ events = await storage.get_events("run_123")
287
+
288
+ # Create context with event log (should replay events)
289
+ ctx = LocalContext(
290
+ run_id="run_123",
291
+ workflow_name="test_workflow",
292
+ storage=storage,
293
+ event_log=events,
294
+ durable=True,
295
+ )
296
+
297
+ # Context should have cancellation requested from replay
298
+ assert ctx.is_cancellation_requested() is True
299
+
300
+
301
+ class TestCancellationErrorHandling:
302
+ """Test CancellationError handling in workflows."""
303
+
304
+ @pytest.mark.asyncio
305
+ async def test_workflow_can_catch_cancellation_for_cleanup(self):
306
+ """Test that workflows can catch CancellationError for cleanup."""
307
+ from pyworkflow.context import LocalContext
308
+
309
+ cleanup_called = False
310
+
311
+ async def workflow_with_cleanup():
312
+ nonlocal cleanup_called
313
+ try:
314
+ # Simulate work that would check cancellation
315
+ ctx = LocalContext(
316
+ run_id="test",
317
+ workflow_name="test",
318
+ storage=None,
319
+ durable=False,
320
+ )
321
+ ctx.request_cancellation()
322
+ ctx.check_cancellation()
323
+ except CancellationError:
324
+ cleanup_called = True
325
+ raise
326
+
327
+ with pytest.raises(CancellationError):
328
+ await workflow_with_cleanup()
329
+
330
+ assert cleanup_called is True