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.
- dashboard/backend/app/__init__.py +1 -0
- dashboard/backend/app/config.py +32 -0
- dashboard/backend/app/controllers/__init__.py +6 -0
- dashboard/backend/app/controllers/run_controller.py +86 -0
- dashboard/backend/app/controllers/workflow_controller.py +33 -0
- dashboard/backend/app/dependencies/__init__.py +5 -0
- dashboard/backend/app/dependencies/storage.py +50 -0
- dashboard/backend/app/repositories/__init__.py +6 -0
- dashboard/backend/app/repositories/run_repository.py +80 -0
- dashboard/backend/app/repositories/workflow_repository.py +27 -0
- dashboard/backend/app/rest/__init__.py +8 -0
- dashboard/backend/app/rest/v1/__init__.py +12 -0
- dashboard/backend/app/rest/v1/health.py +33 -0
- dashboard/backend/app/rest/v1/runs.py +133 -0
- dashboard/backend/app/rest/v1/workflows.py +41 -0
- dashboard/backend/app/schemas/__init__.py +23 -0
- dashboard/backend/app/schemas/common.py +16 -0
- dashboard/backend/app/schemas/event.py +24 -0
- dashboard/backend/app/schemas/hook.py +25 -0
- dashboard/backend/app/schemas/run.py +54 -0
- dashboard/backend/app/schemas/step.py +28 -0
- dashboard/backend/app/schemas/workflow.py +31 -0
- dashboard/backend/app/server.py +87 -0
- dashboard/backend/app/services/__init__.py +6 -0
- dashboard/backend/app/services/run_service.py +240 -0
- dashboard/backend/app/services/workflow_service.py +155 -0
- dashboard/backend/main.py +18 -0
- docs/concepts/cancellation.mdx +362 -0
- docs/concepts/continue-as-new.mdx +434 -0
- docs/concepts/events.mdx +266 -0
- docs/concepts/fault-tolerance.mdx +370 -0
- docs/concepts/hooks.mdx +552 -0
- docs/concepts/limitations.mdx +167 -0
- docs/concepts/schedules.mdx +775 -0
- docs/concepts/sleep.mdx +312 -0
- docs/concepts/steps.mdx +301 -0
- docs/concepts/workflows.mdx +255 -0
- docs/guides/cli.mdx +942 -0
- docs/guides/configuration.mdx +560 -0
- docs/introduction.mdx +155 -0
- docs/quickstart.mdx +279 -0
- examples/__init__.py +1 -0
- examples/celery/__init__.py +1 -0
- examples/celery/durable/docker-compose.yml +55 -0
- examples/celery/durable/pyworkflow.config.yaml +12 -0
- examples/celery/durable/workflows/__init__.py +122 -0
- examples/celery/durable/workflows/basic.py +87 -0
- examples/celery/durable/workflows/batch_processing.py +102 -0
- examples/celery/durable/workflows/cancellation.py +273 -0
- examples/celery/durable/workflows/child_workflow_patterns.py +240 -0
- examples/celery/durable/workflows/child_workflows.py +202 -0
- examples/celery/durable/workflows/continue_as_new.py +260 -0
- examples/celery/durable/workflows/fault_tolerance.py +210 -0
- examples/celery/durable/workflows/hooks.py +211 -0
- examples/celery/durable/workflows/idempotency.py +112 -0
- examples/celery/durable/workflows/long_running.py +99 -0
- examples/celery/durable/workflows/retries.py +101 -0
- examples/celery/durable/workflows/schedules.py +209 -0
- examples/celery/transient/01_basic_workflow.py +91 -0
- examples/celery/transient/02_fault_tolerance.py +257 -0
- examples/celery/transient/__init__.py +20 -0
- examples/celery/transient/pyworkflow.config.yaml +25 -0
- examples/local/__init__.py +1 -0
- examples/local/durable/01_basic_workflow.py +94 -0
- examples/local/durable/02_file_storage.py +132 -0
- examples/local/durable/03_retries.py +169 -0
- examples/local/durable/04_long_running.py +119 -0
- examples/local/durable/05_event_log.py +145 -0
- examples/local/durable/06_idempotency.py +148 -0
- examples/local/durable/07_hooks.py +334 -0
- examples/local/durable/08_cancellation.py +233 -0
- examples/local/durable/09_child_workflows.py +198 -0
- examples/local/durable/10_child_workflow_patterns.py +265 -0
- examples/local/durable/11_continue_as_new.py +249 -0
- examples/local/durable/12_schedules.py +198 -0
- examples/local/durable/__init__.py +1 -0
- examples/local/transient/01_quick_tasks.py +87 -0
- examples/local/transient/02_retries.py +130 -0
- examples/local/transient/03_sleep.py +141 -0
- examples/local/transient/__init__.py +1 -0
- pyworkflow/__init__.py +256 -0
- pyworkflow/aws/__init__.py +68 -0
- pyworkflow/aws/context.py +234 -0
- pyworkflow/aws/handler.py +184 -0
- pyworkflow/aws/testing.py +310 -0
- pyworkflow/celery/__init__.py +41 -0
- pyworkflow/celery/app.py +198 -0
- pyworkflow/celery/scheduler.py +315 -0
- pyworkflow/celery/tasks.py +1746 -0
- pyworkflow/cli/__init__.py +132 -0
- pyworkflow/cli/__main__.py +6 -0
- pyworkflow/cli/commands/__init__.py +1 -0
- pyworkflow/cli/commands/hooks.py +640 -0
- pyworkflow/cli/commands/quickstart.py +495 -0
- pyworkflow/cli/commands/runs.py +773 -0
- pyworkflow/cli/commands/scheduler.py +130 -0
- pyworkflow/cli/commands/schedules.py +794 -0
- pyworkflow/cli/commands/setup.py +703 -0
- pyworkflow/cli/commands/worker.py +413 -0
- pyworkflow/cli/commands/workflows.py +1257 -0
- pyworkflow/cli/output/__init__.py +1 -0
- pyworkflow/cli/output/formatters.py +321 -0
- pyworkflow/cli/output/styles.py +121 -0
- pyworkflow/cli/utils/__init__.py +1 -0
- pyworkflow/cli/utils/async_helpers.py +30 -0
- pyworkflow/cli/utils/config.py +130 -0
- pyworkflow/cli/utils/config_generator.py +344 -0
- pyworkflow/cli/utils/discovery.py +53 -0
- pyworkflow/cli/utils/docker_manager.py +651 -0
- pyworkflow/cli/utils/interactive.py +364 -0
- pyworkflow/cli/utils/storage.py +115 -0
- pyworkflow/config.py +329 -0
- pyworkflow/context/__init__.py +63 -0
- pyworkflow/context/aws.py +230 -0
- pyworkflow/context/base.py +416 -0
- pyworkflow/context/local.py +930 -0
- pyworkflow/context/mock.py +381 -0
- pyworkflow/core/__init__.py +0 -0
- pyworkflow/core/exceptions.py +353 -0
- pyworkflow/core/registry.py +313 -0
- pyworkflow/core/scheduled.py +328 -0
- pyworkflow/core/step.py +494 -0
- pyworkflow/core/workflow.py +294 -0
- pyworkflow/discovery.py +248 -0
- pyworkflow/engine/__init__.py +0 -0
- pyworkflow/engine/events.py +879 -0
- pyworkflow/engine/executor.py +682 -0
- pyworkflow/engine/replay.py +273 -0
- pyworkflow/observability/__init__.py +19 -0
- pyworkflow/observability/logging.py +234 -0
- pyworkflow/primitives/__init__.py +33 -0
- pyworkflow/primitives/child_handle.py +174 -0
- pyworkflow/primitives/child_workflow.py +372 -0
- pyworkflow/primitives/continue_as_new.py +101 -0
- pyworkflow/primitives/define_hook.py +150 -0
- pyworkflow/primitives/hooks.py +97 -0
- pyworkflow/primitives/resume_hook.py +210 -0
- pyworkflow/primitives/schedule.py +545 -0
- pyworkflow/primitives/shield.py +96 -0
- pyworkflow/primitives/sleep.py +100 -0
- pyworkflow/runtime/__init__.py +21 -0
- pyworkflow/runtime/base.py +179 -0
- pyworkflow/runtime/celery.py +310 -0
- pyworkflow/runtime/factory.py +101 -0
- pyworkflow/runtime/local.py +706 -0
- pyworkflow/scheduler/__init__.py +9 -0
- pyworkflow/scheduler/local.py +248 -0
- pyworkflow/serialization/__init__.py +0 -0
- pyworkflow/serialization/decoder.py +146 -0
- pyworkflow/serialization/encoder.py +162 -0
- pyworkflow/storage/__init__.py +54 -0
- pyworkflow/storage/base.py +612 -0
- pyworkflow/storage/config.py +185 -0
- pyworkflow/storage/dynamodb.py +1315 -0
- pyworkflow/storage/file.py +827 -0
- pyworkflow/storage/memory.py +549 -0
- pyworkflow/storage/postgres.py +1161 -0
- pyworkflow/storage/schemas.py +486 -0
- pyworkflow/storage/sqlite.py +1136 -0
- pyworkflow/utils/__init__.py +0 -0
- pyworkflow/utils/duration.py +177 -0
- pyworkflow/utils/schedule.py +391 -0
- pyworkflow_engine-0.1.7.dist-info/METADATA +687 -0
- pyworkflow_engine-0.1.7.dist-info/RECORD +196 -0
- pyworkflow_engine-0.1.7.dist-info/WHEEL +5 -0
- pyworkflow_engine-0.1.7.dist-info/entry_points.txt +2 -0
- pyworkflow_engine-0.1.7.dist-info/licenses/LICENSE +21 -0
- pyworkflow_engine-0.1.7.dist-info/top_level.txt +5 -0
- tests/examples/__init__.py +0 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_cancellation.py +330 -0
- tests/integration/test_child_workflows.py +439 -0
- tests/integration/test_continue_as_new.py +428 -0
- tests/integration/test_dynamodb_storage.py +1146 -0
- tests/integration/test_fault_tolerance.py +369 -0
- tests/integration/test_schedule_storage.py +484 -0
- tests/unit/__init__.py +0 -0
- tests/unit/backends/__init__.py +1 -0
- tests/unit/backends/test_dynamodb_storage.py +1554 -0
- tests/unit/backends/test_postgres_storage.py +1281 -0
- tests/unit/backends/test_sqlite_storage.py +1460 -0
- tests/unit/conftest.py +41 -0
- tests/unit/test_cancellation.py +364 -0
- tests/unit/test_child_workflows.py +680 -0
- tests/unit/test_continue_as_new.py +441 -0
- tests/unit/test_event_limits.py +316 -0
- tests/unit/test_executor.py +320 -0
- tests/unit/test_fault_tolerance.py +334 -0
- tests/unit/test_hooks.py +495 -0
- tests/unit/test_registry.py +261 -0
- tests/unit/test_replay.py +420 -0
- tests/unit/test_schedule_schemas.py +285 -0
- tests/unit/test_schedule_utils.py +286 -0
- tests/unit/test_scheduled_workflow.py +274 -0
- tests/unit/test_step.py +353 -0
- tests/unit/test_workflow.py +243 -0
|
@@ -0,0 +1,362 @@
|
|
|
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>
|