pyworkflow-engine 0.1.7__py3-none-any.whl → 0.1.9__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 (145) hide show
  1. pyworkflow/__init__.py +10 -1
  2. pyworkflow/celery/tasks.py +272 -24
  3. pyworkflow/cli/__init__.py +4 -1
  4. pyworkflow/cli/commands/runs.py +4 -4
  5. pyworkflow/cli/commands/setup.py +203 -4
  6. pyworkflow/cli/utils/config_generator.py +76 -3
  7. pyworkflow/cli/utils/docker_manager.py +232 -0
  8. pyworkflow/context/__init__.py +13 -0
  9. pyworkflow/context/base.py +26 -0
  10. pyworkflow/context/local.py +80 -0
  11. pyworkflow/context/step_context.py +295 -0
  12. pyworkflow/core/registry.py +6 -1
  13. pyworkflow/core/step.py +141 -0
  14. pyworkflow/core/workflow.py +56 -0
  15. pyworkflow/engine/events.py +30 -0
  16. pyworkflow/engine/replay.py +39 -0
  17. pyworkflow/primitives/child_workflow.py +1 -1
  18. pyworkflow/runtime/local.py +1 -1
  19. pyworkflow/storage/__init__.py +14 -0
  20. pyworkflow/storage/base.py +35 -0
  21. pyworkflow/storage/cassandra.py +1747 -0
  22. pyworkflow/storage/config.py +69 -0
  23. pyworkflow/storage/dynamodb.py +31 -2
  24. pyworkflow/storage/file.py +28 -0
  25. pyworkflow/storage/memory.py +18 -0
  26. pyworkflow/storage/mysql.py +1159 -0
  27. pyworkflow/storage/postgres.py +27 -2
  28. pyworkflow/storage/schemas.py +4 -3
  29. pyworkflow/storage/sqlite.py +25 -2
  30. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/METADATA +7 -4
  31. pyworkflow_engine-0.1.9.dist-info/RECORD +91 -0
  32. pyworkflow_engine-0.1.9.dist-info/top_level.txt +1 -0
  33. dashboard/backend/app/__init__.py +0 -1
  34. dashboard/backend/app/config.py +0 -32
  35. dashboard/backend/app/controllers/__init__.py +0 -6
  36. dashboard/backend/app/controllers/run_controller.py +0 -86
  37. dashboard/backend/app/controllers/workflow_controller.py +0 -33
  38. dashboard/backend/app/dependencies/__init__.py +0 -5
  39. dashboard/backend/app/dependencies/storage.py +0 -50
  40. dashboard/backend/app/repositories/__init__.py +0 -6
  41. dashboard/backend/app/repositories/run_repository.py +0 -80
  42. dashboard/backend/app/repositories/workflow_repository.py +0 -27
  43. dashboard/backend/app/rest/__init__.py +0 -8
  44. dashboard/backend/app/rest/v1/__init__.py +0 -12
  45. dashboard/backend/app/rest/v1/health.py +0 -33
  46. dashboard/backend/app/rest/v1/runs.py +0 -133
  47. dashboard/backend/app/rest/v1/workflows.py +0 -41
  48. dashboard/backend/app/schemas/__init__.py +0 -23
  49. dashboard/backend/app/schemas/common.py +0 -16
  50. dashboard/backend/app/schemas/event.py +0 -24
  51. dashboard/backend/app/schemas/hook.py +0 -25
  52. dashboard/backend/app/schemas/run.py +0 -54
  53. dashboard/backend/app/schemas/step.py +0 -28
  54. dashboard/backend/app/schemas/workflow.py +0 -31
  55. dashboard/backend/app/server.py +0 -87
  56. dashboard/backend/app/services/__init__.py +0 -6
  57. dashboard/backend/app/services/run_service.py +0 -240
  58. dashboard/backend/app/services/workflow_service.py +0 -155
  59. dashboard/backend/main.py +0 -18
  60. docs/concepts/cancellation.mdx +0 -362
  61. docs/concepts/continue-as-new.mdx +0 -434
  62. docs/concepts/events.mdx +0 -266
  63. docs/concepts/fault-tolerance.mdx +0 -370
  64. docs/concepts/hooks.mdx +0 -552
  65. docs/concepts/limitations.mdx +0 -167
  66. docs/concepts/schedules.mdx +0 -775
  67. docs/concepts/sleep.mdx +0 -312
  68. docs/concepts/steps.mdx +0 -301
  69. docs/concepts/workflows.mdx +0 -255
  70. docs/guides/cli.mdx +0 -942
  71. docs/guides/configuration.mdx +0 -560
  72. docs/introduction.mdx +0 -155
  73. docs/quickstart.mdx +0 -279
  74. examples/__init__.py +0 -1
  75. examples/celery/__init__.py +0 -1
  76. examples/celery/durable/docker-compose.yml +0 -55
  77. examples/celery/durable/pyworkflow.config.yaml +0 -12
  78. examples/celery/durable/workflows/__init__.py +0 -122
  79. examples/celery/durable/workflows/basic.py +0 -87
  80. examples/celery/durable/workflows/batch_processing.py +0 -102
  81. examples/celery/durable/workflows/cancellation.py +0 -273
  82. examples/celery/durable/workflows/child_workflow_patterns.py +0 -240
  83. examples/celery/durable/workflows/child_workflows.py +0 -202
  84. examples/celery/durable/workflows/continue_as_new.py +0 -260
  85. examples/celery/durable/workflows/fault_tolerance.py +0 -210
  86. examples/celery/durable/workflows/hooks.py +0 -211
  87. examples/celery/durable/workflows/idempotency.py +0 -112
  88. examples/celery/durable/workflows/long_running.py +0 -99
  89. examples/celery/durable/workflows/retries.py +0 -101
  90. examples/celery/durable/workflows/schedules.py +0 -209
  91. examples/celery/transient/01_basic_workflow.py +0 -91
  92. examples/celery/transient/02_fault_tolerance.py +0 -257
  93. examples/celery/transient/__init__.py +0 -20
  94. examples/celery/transient/pyworkflow.config.yaml +0 -25
  95. examples/local/__init__.py +0 -1
  96. examples/local/durable/01_basic_workflow.py +0 -94
  97. examples/local/durable/02_file_storage.py +0 -132
  98. examples/local/durable/03_retries.py +0 -169
  99. examples/local/durable/04_long_running.py +0 -119
  100. examples/local/durable/05_event_log.py +0 -145
  101. examples/local/durable/06_idempotency.py +0 -148
  102. examples/local/durable/07_hooks.py +0 -334
  103. examples/local/durable/08_cancellation.py +0 -233
  104. examples/local/durable/09_child_workflows.py +0 -198
  105. examples/local/durable/10_child_workflow_patterns.py +0 -265
  106. examples/local/durable/11_continue_as_new.py +0 -249
  107. examples/local/durable/12_schedules.py +0 -198
  108. examples/local/durable/__init__.py +0 -1
  109. examples/local/transient/01_quick_tasks.py +0 -87
  110. examples/local/transient/02_retries.py +0 -130
  111. examples/local/transient/03_sleep.py +0 -141
  112. examples/local/transient/__init__.py +0 -1
  113. pyworkflow_engine-0.1.7.dist-info/RECORD +0 -196
  114. pyworkflow_engine-0.1.7.dist-info/top_level.txt +0 -5
  115. tests/examples/__init__.py +0 -0
  116. tests/integration/__init__.py +0 -0
  117. tests/integration/test_cancellation.py +0 -330
  118. tests/integration/test_child_workflows.py +0 -439
  119. tests/integration/test_continue_as_new.py +0 -428
  120. tests/integration/test_dynamodb_storage.py +0 -1146
  121. tests/integration/test_fault_tolerance.py +0 -369
  122. tests/integration/test_schedule_storage.py +0 -484
  123. tests/unit/__init__.py +0 -0
  124. tests/unit/backends/__init__.py +0 -1
  125. tests/unit/backends/test_dynamodb_storage.py +0 -1554
  126. tests/unit/backends/test_postgres_storage.py +0 -1281
  127. tests/unit/backends/test_sqlite_storage.py +0 -1460
  128. tests/unit/conftest.py +0 -41
  129. tests/unit/test_cancellation.py +0 -364
  130. tests/unit/test_child_workflows.py +0 -680
  131. tests/unit/test_continue_as_new.py +0 -441
  132. tests/unit/test_event_limits.py +0 -316
  133. tests/unit/test_executor.py +0 -320
  134. tests/unit/test_fault_tolerance.py +0 -334
  135. tests/unit/test_hooks.py +0 -495
  136. tests/unit/test_registry.py +0 -261
  137. tests/unit/test_replay.py +0 -420
  138. tests/unit/test_schedule_schemas.py +0 -285
  139. tests/unit/test_schedule_utils.py +0 -286
  140. tests/unit/test_scheduled_workflow.py +0 -274
  141. tests/unit/test_step.py +0 -353
  142. tests/unit/test_workflow.py +0 -243
  143. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/WHEEL +0 -0
  144. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/entry_points.txt +0 -0
  145. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,362 +0,0 @@
1
- ---
2
- title: 'Cancellation'
3
- description: 'Gracefully cancel running or suspended workflows'
4
- ---
5
-
6
- ## Overview
7
-
8
- PyWorkflow supports graceful workflow cancellation. When you cancel a workflow, it will terminate at the next **checkpoint** rather than being forcefully killed, allowing for proper cleanup.
9
-
10
- <CardGroup cols={2}>
11
- <Card title="Graceful Termination" icon="hand">
12
- Workflows stop at safe checkpoints, not mid-operation.
13
- </Card>
14
- <Card title="Cleanup Support" icon="broom">
15
- Catch `CancellationError` to perform cleanup before terminating.
16
- </Card>
17
- <Card title="Shield Critical Code" icon="shield">
18
- Use `shield()` to protect code that must complete.
19
- </Card>
20
- <Card title="CLI & API" icon="terminal">
21
- Cancel via CLI command or programmatic API.
22
- </Card>
23
- </CardGroup>
24
-
25
- ## Cancelling a Workflow
26
-
27
- <Tabs>
28
- <Tab title="Python API">
29
- Use `cancel_workflow()` to request cancellation:
30
-
31
- ```python
32
- from pyworkflow import cancel_workflow
33
-
34
- # Request cancellation
35
- cancelled = await cancel_workflow("run_abc123")
36
-
37
- # With a reason
38
- cancelled = await cancel_workflow(
39
- "run_abc123",
40
- reason="User requested cancellation"
41
- )
42
-
43
- # Wait for cancellation to complete
44
- cancelled = await cancel_workflow(
45
- "run_abc123",
46
- wait=True,
47
- timeout=30
48
- )
49
- ```
50
- </Tab>
51
- <Tab title="CLI">
52
- Use the `runs cancel` command:
53
-
54
- ```bash
55
- # Cancel a workflow
56
- pyworkflow runs cancel run_abc123
57
-
58
- # Cancel with reason
59
- pyworkflow runs cancel run_abc123 --reason "User requested"
60
-
61
- # Wait for cancellation to complete
62
- pyworkflow runs cancel run_abc123 --wait
63
-
64
- # Wait with timeout
65
- pyworkflow runs cancel run_abc123 --wait --timeout 60
66
- ```
67
- </Tab>
68
- </Tabs>
69
-
70
- ## How Cancellation Works
71
-
72
- Cancellation in PyWorkflow is **checkpoint-based**. The workflow is cancelled at the next checkpoint, not immediately.
73
-
74
- ### Cancellation Checkpoints
75
-
76
- Cancellation is checked at these points:
77
-
78
- | Checkpoint | When |
79
- |------------|------|
80
- | **Before each step** | Before `@step` decorated functions execute |
81
- | **Before sleep** | Before `await sleep()` suspends the workflow |
82
- | **Before hook** | Before `await hook()` suspends the workflow |
83
-
84
- ```
85
- ┌─────────────────────────────────────────────────────────────────────┐
86
- │ Workflow Execution with Cancellation Checkpoints │
87
- │ │
88
- │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
89
- │ │ ✓ Check │───▶│ Step 1 │───▶│ ✓ Check │───▶│ Step 2 │ │
90
- │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
91
- │ │ │
92
- │ ▼ │
93
- │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
94
- │ │ Step 3 │◀───│ ✓ Check │◀───│ sleep() │◀───│ ✓ Check │ │
95
- │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
96
- │ │
97
- │ ✓ Check = Cancellation checkpoint │
98
- └─────────────────────────────────────────────────────────────────────┘
99
- ```
100
-
101
- <Warning>
102
- **Important:** Cancellation does NOT interrupt a step that is already executing.
103
-
104
- If a step takes a long time (e.g., a 10-minute API call), the workflow will only detect cancellation after that step completes. This is by design to avoid leaving operations in an inconsistent state.
105
- </Warning>
106
-
107
- ### Cooperative Cancellation for Long-Running Steps
108
-
109
- For steps that run for a long time, you can add **cooperative cancellation checks**:
110
-
111
- ```python
112
- from pyworkflow import step, get_context
113
-
114
- @step()
115
- async def process_large_dataset(dataset_id: str):
116
- ctx = get_context()
117
- dataset = await load_dataset(dataset_id)
118
-
119
- results = []
120
- for chunk in dataset.chunks():
121
- # Check for cancellation periodically
122
- ctx.check_cancellation()
123
-
124
- result = await process_chunk(chunk)
125
- results.append(result)
126
-
127
- return results
128
- ```
129
-
130
- This allows the step to respond to cancellation requests between chunks rather than waiting until the entire dataset is processed.
131
-
132
- ## Handling Cancellation
133
-
134
- Workflows can catch `CancellationError` to perform cleanup before terminating:
135
-
136
- ```python
137
- from pyworkflow import workflow, step, CancellationError, shield
138
-
139
- @workflow()
140
- async def order_workflow(order_id: str):
141
- try:
142
- await reserve_inventory(order_id)
143
- await charge_payment(order_id)
144
- await create_shipment(order_id)
145
- return {"status": "completed"}
146
-
147
- except CancellationError:
148
- # Cleanup on cancellation
149
- async with shield():
150
- # This cleanup will complete even if cancelled
151
- await release_inventory(order_id)
152
- await refund_payment(order_id)
153
- raise # Re-raise to mark workflow as cancelled
154
- ```
155
-
156
- ### The `shield()` Context Manager
157
-
158
- Use `shield()` to protect critical code from cancellation:
159
-
160
- ```python
161
- from pyworkflow import shield
162
-
163
- async with shield():
164
- # This code will complete even if cancellation is requested
165
- await commit_transaction()
166
- await send_confirmation_email()
167
- ```
168
-
169
- While inside a `shield()` block:
170
- - `ctx.check_cancellation()` will not raise `CancellationError`
171
- - The cancellation request is preserved
172
- - Cancellation will take effect after exiting the shield
173
-
174
- <Warning>
175
- Don't use `shield()` for long-running operations as it defeats the purpose of graceful cancellation.
176
- </Warning>
177
-
178
- ## Workflow States
179
-
180
- When a workflow is cancelled, its status transitions to `CANCELLED`:
181
-
182
- ```
183
- ┌─────────────┐
184
- │ RUNNING │
185
- └──────┬──────┘
186
-
187
- │ cancel_workflow()
188
-
189
- ┌─────────────┐
190
- │ CANCELLED │
191
- └─────────────┘
192
-
193
- ┌─────────────┐
194
- │ SUSPENDED │ (sleeping or waiting for hook)
195
- └──────┬──────┘
196
-
197
- │ cancel_workflow()
198
-
199
- ┌─────────────┐
200
- │ CANCELLED │ (immediate, no resume needed)
201
- └─────────────┘
202
- ```
203
-
204
- | Workflow State | Cancellation Behavior |
205
- |----------------|----------------------|
206
- | `RUNNING` | Sets cancellation flag; workflow cancelled at next checkpoint |
207
- | `SUSPENDED` | Immediately marked as `CANCELLED`; scheduled resume task is abandoned |
208
- | `COMPLETED` | Cannot be cancelled (returns `False`) |
209
- | `FAILED` | Cannot be cancelled (returns `False`) |
210
- | `CANCELLED` | Already cancelled (returns `False`) |
211
-
212
- ## Monitoring Cancelled Workflows
213
-
214
- Use the CLI to view cancelled workflows:
215
-
216
- ```bash
217
- # List cancelled workflows
218
- pyworkflow runs list --status cancelled
219
-
220
- # View details of a cancelled workflow
221
- pyworkflow runs status run_abc123
222
-
223
- # View event log including cancellation events
224
- pyworkflow runs logs run_abc123
225
- ```
226
-
227
- Example output:
228
- ```
229
- $ pyworkflow runs logs run_abc123 --filter cancellation
230
-
231
- 1
232
- Type: cancellation.requested
233
- Timestamp: 10:30:45.123
234
- Data: {
235
- "reason": "User requested cancellation",
236
- "requested_by": "admin"
237
- }
238
-
239
- 2
240
- Type: workflow.cancelled
241
- Timestamp: 10:30:45.456
242
- Data: {
243
- "reason": "User requested cancellation",
244
- "cleanup_completed": true
245
- }
246
- ```
247
-
248
- ## Best Practices
249
-
250
- <AccordionGroup>
251
- <Accordion title="Always handle CancellationError for cleanup">
252
- If your workflow allocates resources or makes changes that need to be reversed, catch `CancellationError` and clean up:
253
-
254
- ```python
255
- @workflow()
256
- async def managed_workflow():
257
- resource = await acquire_resource()
258
- try:
259
- await use_resource(resource)
260
- except CancellationError:
261
- await release_resource(resource)
262
- raise
263
- ```
264
- </Accordion>
265
-
266
- <Accordion title="Use shield() sparingly">
267
- Only use `shield()` for truly critical operations like database commits or compensation logic. Long-running shielded operations delay cancellation.
268
-
269
- ```python
270
- # Good: Short critical operation
271
- async with shield():
272
- await db.commit()
273
-
274
- # Bad: Long operation in shield
275
- async with shield():
276
- await process_million_records() # Defeats cancellation
277
- ```
278
- </Accordion>
279
-
280
- <Accordion title="Add cooperative checks in long steps">
281
- For steps that process large amounts of data, add periodic cancellation checks:
282
-
283
- ```python
284
- @step()
285
- async def batch_process(items: list):
286
- ctx = get_context()
287
- for i, item in enumerate(items):
288
- if i % 100 == 0: # Check every 100 items
289
- ctx.check_cancellation()
290
- await process_item(item)
291
- ```
292
- </Accordion>
293
-
294
- <Accordion title="Provide cancellation reasons">
295
- Include a reason when cancelling for better debugging and audit trails:
296
-
297
- ```python
298
- await cancel_workflow(
299
- run_id,
300
- reason="Customer cancelled order #12345"
301
- )
302
- ```
303
- </Accordion>
304
- </AccordionGroup>
305
-
306
- ## API Reference
307
-
308
- ### `cancel_workflow()`
309
-
310
- ```python
311
- async def cancel_workflow(
312
- run_id: str,
313
- reason: Optional[str] = None,
314
- wait: bool = False,
315
- timeout: Optional[float] = None,
316
- storage: Optional[StorageBackend] = None,
317
- ) -> bool
318
- ```
319
-
320
- | Parameter | Type | Default | Description |
321
- |-----------|------|---------|-------------|
322
- | `run_id` | `str` | required | Workflow run ID to cancel |
323
- | `reason` | `str` | `None` | Optional reason for cancellation |
324
- | `wait` | `bool` | `False` | Wait for workflow to reach terminal state |
325
- | `timeout` | `float` | `None` | Timeout in seconds when waiting |
326
- | `storage` | `StorageBackend` | `None` | Storage backend (uses configured default) |
327
-
328
- **Returns:** `True` if cancellation was initiated, `False` if workflow is already in a terminal state.
329
-
330
- ### `CancellationError`
331
-
332
- ```python
333
- class CancellationError(WorkflowError):
334
- message: str # Description of the cancellation
335
- reason: str # Optional reason (e.g., "user_requested")
336
- ```
337
-
338
- ### `shield()`
339
-
340
- ```python
341
- @asynccontextmanager
342
- async def shield() -> AsyncIterator[None]
343
- ```
344
-
345
- Context manager that prevents cancellation checks from raising within its scope.
346
-
347
- ## Next Steps
348
-
349
- <CardGroup cols={2}>
350
- <Card title="Fault Tolerance" icon="shield-check" href="/concepts/fault-tolerance">
351
- Learn about automatic recovery from worker crashes.
352
- </Card>
353
- <Card title="Hooks" icon="webhook" href="/concepts/hooks">
354
- Wait for external events in your workflows.
355
- </Card>
356
- <Card title="CLI Guide" icon="terminal" href="/guides/cli">
357
- Manage workflows from the command line.
358
- </Card>
359
- <Card title="Sleep" icon="clock" href="/concepts/sleep">
360
- Pause workflows with durable sleep.
361
- </Card>
362
- </CardGroup>