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,879 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event types and schemas for event sourcing.
|
|
3
|
+
|
|
4
|
+
All workflow state changes are recorded as events in an append-only log.
|
|
5
|
+
Events enable deterministic replay for fault tolerance and resumption.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import uuid
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EventType(Enum):
|
|
16
|
+
"""All possible event types in the workflow system."""
|
|
17
|
+
|
|
18
|
+
# Workflow lifecycle events
|
|
19
|
+
WORKFLOW_STARTED = "workflow.started"
|
|
20
|
+
WORKFLOW_COMPLETED = "workflow.completed"
|
|
21
|
+
WORKFLOW_FAILED = "workflow.failed"
|
|
22
|
+
WORKFLOW_INTERRUPTED = "workflow.interrupted" # Infrastructure failure (worker loss)
|
|
23
|
+
WORKFLOW_CANCELLED = "workflow.cancelled"
|
|
24
|
+
WORKFLOW_PAUSED = "workflow.paused"
|
|
25
|
+
WORKFLOW_RESUMED = "workflow.resumed"
|
|
26
|
+
WORKFLOW_CONTINUED_AS_NEW = "workflow.continued_as_new" # Workflow continued with fresh history
|
|
27
|
+
|
|
28
|
+
# Step lifecycle events
|
|
29
|
+
STEP_STARTED = "step.started"
|
|
30
|
+
STEP_COMPLETED = "step.completed"
|
|
31
|
+
STEP_FAILED = "step.failed"
|
|
32
|
+
STEP_RETRYING = "step.retrying"
|
|
33
|
+
STEP_CANCELLED = "step.cancelled"
|
|
34
|
+
|
|
35
|
+
# Sleep/wait events
|
|
36
|
+
SLEEP_STARTED = "sleep.started"
|
|
37
|
+
SLEEP_COMPLETED = "sleep.completed"
|
|
38
|
+
|
|
39
|
+
# Hook/webhook events
|
|
40
|
+
HOOK_CREATED = "hook.created"
|
|
41
|
+
HOOK_RECEIVED = "hook.received"
|
|
42
|
+
HOOK_EXPIRED = "hook.expired"
|
|
43
|
+
HOOK_DISPOSED = "hook.disposed"
|
|
44
|
+
|
|
45
|
+
# Cancellation events
|
|
46
|
+
CANCELLATION_REQUESTED = "cancellation.requested"
|
|
47
|
+
|
|
48
|
+
# Child workflow events
|
|
49
|
+
CHILD_WORKFLOW_STARTED = "child_workflow.started"
|
|
50
|
+
CHILD_WORKFLOW_COMPLETED = "child_workflow.completed"
|
|
51
|
+
CHILD_WORKFLOW_FAILED = "child_workflow.failed"
|
|
52
|
+
CHILD_WORKFLOW_CANCELLED = "child_workflow.cancelled"
|
|
53
|
+
|
|
54
|
+
# Schedule events
|
|
55
|
+
SCHEDULE_CREATED = "schedule.created"
|
|
56
|
+
SCHEDULE_UPDATED = "schedule.updated"
|
|
57
|
+
SCHEDULE_PAUSED = "schedule.paused"
|
|
58
|
+
SCHEDULE_RESUMED = "schedule.resumed"
|
|
59
|
+
SCHEDULE_DELETED = "schedule.deleted"
|
|
60
|
+
SCHEDULE_TRIGGERED = "schedule.triggered"
|
|
61
|
+
SCHEDULE_SKIPPED = "schedule.skipped"
|
|
62
|
+
SCHEDULE_BACKFILL_STARTED = "schedule.backfill_started"
|
|
63
|
+
SCHEDULE_BACKFILL_COMPLETED = "schedule.backfill_completed"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Event:
|
|
68
|
+
"""
|
|
69
|
+
Base event structure for all workflow events.
|
|
70
|
+
|
|
71
|
+
Events are immutable records of state changes, stored in an append-only log.
|
|
72
|
+
The sequence number is assigned by the storage layer to ensure ordering.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
event_id: str = field(default_factory=lambda: f"evt_{uuid.uuid4().hex[:16]}")
|
|
76
|
+
run_id: str = ""
|
|
77
|
+
type: EventType = EventType.WORKFLOW_STARTED
|
|
78
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
79
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
80
|
+
sequence: int | None = None # Assigned by storage layer
|
|
81
|
+
|
|
82
|
+
def __post_init__(self) -> None:
|
|
83
|
+
"""Validate event after initialization."""
|
|
84
|
+
if not self.run_id:
|
|
85
|
+
raise ValueError("Event must have a run_id")
|
|
86
|
+
if not isinstance(self.type, EventType):
|
|
87
|
+
raise TypeError(f"Event type must be EventType enum, got {type(self.type)}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Event creation helpers for common event types
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def create_workflow_started_event(
|
|
94
|
+
run_id: str,
|
|
95
|
+
workflow_name: str,
|
|
96
|
+
args: Any,
|
|
97
|
+
kwargs: Any,
|
|
98
|
+
metadata: dict[str, Any] | None = None,
|
|
99
|
+
) -> Event:
|
|
100
|
+
"""Create a workflow started event."""
|
|
101
|
+
return Event(
|
|
102
|
+
run_id=run_id,
|
|
103
|
+
type=EventType.WORKFLOW_STARTED,
|
|
104
|
+
data={
|
|
105
|
+
"workflow_name": workflow_name,
|
|
106
|
+
"args": args,
|
|
107
|
+
"kwargs": kwargs,
|
|
108
|
+
"metadata": metadata or {},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def create_workflow_completed_event(run_id: str, result: Any, workflow_name: str) -> Event:
|
|
114
|
+
"""Create a workflow completed event."""
|
|
115
|
+
return Event(
|
|
116
|
+
run_id=run_id,
|
|
117
|
+
type=EventType.WORKFLOW_COMPLETED,
|
|
118
|
+
data={"result": result, "workflow_name": workflow_name},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def create_workflow_failed_event(
|
|
123
|
+
run_id: str, error: str, error_type: str, traceback: str | None = None
|
|
124
|
+
) -> Event:
|
|
125
|
+
"""Create a workflow failed event."""
|
|
126
|
+
return Event(
|
|
127
|
+
run_id=run_id,
|
|
128
|
+
type=EventType.WORKFLOW_FAILED,
|
|
129
|
+
data={
|
|
130
|
+
"error": error,
|
|
131
|
+
"error_type": error_type,
|
|
132
|
+
"traceback": traceback,
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def create_workflow_continued_as_new_event(
|
|
138
|
+
run_id: str,
|
|
139
|
+
new_run_id: str,
|
|
140
|
+
args: str,
|
|
141
|
+
kwargs: str,
|
|
142
|
+
reason: str | None = None,
|
|
143
|
+
) -> Event:
|
|
144
|
+
"""
|
|
145
|
+
Create a workflow continued as new event.
|
|
146
|
+
|
|
147
|
+
This event is recorded when a workflow completes by calling
|
|
148
|
+
continue_as_new(), indicating this run is complete and a new
|
|
149
|
+
run has been started with fresh event history.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
run_id: The current workflow run ID
|
|
153
|
+
new_run_id: The new workflow run ID
|
|
154
|
+
args: Serialized positional arguments for new workflow
|
|
155
|
+
kwargs: Serialized keyword arguments for new workflow
|
|
156
|
+
reason: Optional reason for continuation
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Event: The workflow continued as new event
|
|
160
|
+
"""
|
|
161
|
+
return Event(
|
|
162
|
+
run_id=run_id,
|
|
163
|
+
type=EventType.WORKFLOW_CONTINUED_AS_NEW,
|
|
164
|
+
data={
|
|
165
|
+
"new_run_id": new_run_id,
|
|
166
|
+
"args": args,
|
|
167
|
+
"kwargs": kwargs,
|
|
168
|
+
"reason": reason,
|
|
169
|
+
"continued_at": datetime.now(UTC).isoformat(),
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def create_workflow_interrupted_event(
|
|
175
|
+
run_id: str,
|
|
176
|
+
reason: str,
|
|
177
|
+
worker_id: str | None = None,
|
|
178
|
+
last_event_sequence: int | None = None,
|
|
179
|
+
error: str | None = None,
|
|
180
|
+
recovery_attempt: int = 1,
|
|
181
|
+
recoverable: bool = True,
|
|
182
|
+
) -> Event:
|
|
183
|
+
"""
|
|
184
|
+
Create a workflow interrupted event.
|
|
185
|
+
|
|
186
|
+
This event is recorded when a workflow is interrupted due to infrastructure
|
|
187
|
+
failures (e.g., worker crash, timeout, signal) rather than application errors.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
run_id: The workflow run ID
|
|
191
|
+
reason: Interruption reason (e.g., "worker_lost", "timeout", "signal")
|
|
192
|
+
worker_id: ID of the worker that was handling the task
|
|
193
|
+
last_event_sequence: Sequence number of the last recorded event
|
|
194
|
+
error: Optional error message
|
|
195
|
+
recovery_attempt: Current recovery attempt number
|
|
196
|
+
recoverable: Whether the workflow can be recovered
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Event: The workflow interrupted event
|
|
200
|
+
"""
|
|
201
|
+
return Event(
|
|
202
|
+
run_id=run_id,
|
|
203
|
+
type=EventType.WORKFLOW_INTERRUPTED,
|
|
204
|
+
data={
|
|
205
|
+
"reason": reason,
|
|
206
|
+
"worker_id": worker_id,
|
|
207
|
+
"last_event_sequence": last_event_sequence,
|
|
208
|
+
"error": error,
|
|
209
|
+
"recovery_attempt": recovery_attempt,
|
|
210
|
+
"recoverable": recoverable,
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def create_step_started_event(
|
|
216
|
+
run_id: str,
|
|
217
|
+
step_id: str,
|
|
218
|
+
step_name: str,
|
|
219
|
+
args: Any,
|
|
220
|
+
kwargs: Any,
|
|
221
|
+
attempt: int = 1,
|
|
222
|
+
) -> Event:
|
|
223
|
+
"""Create a step started event."""
|
|
224
|
+
return Event(
|
|
225
|
+
run_id=run_id,
|
|
226
|
+
type=EventType.STEP_STARTED,
|
|
227
|
+
data={
|
|
228
|
+
"step_id": step_id,
|
|
229
|
+
"step_name": step_name,
|
|
230
|
+
"args": args,
|
|
231
|
+
"kwargs": kwargs,
|
|
232
|
+
"attempt": attempt,
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def create_step_completed_event(run_id: str, step_id: str, result: Any, step_name: str) -> Event:
|
|
238
|
+
"""Create a step completed event."""
|
|
239
|
+
return Event(
|
|
240
|
+
run_id=run_id,
|
|
241
|
+
type=EventType.STEP_COMPLETED,
|
|
242
|
+
data={
|
|
243
|
+
"step_id": step_id,
|
|
244
|
+
"result": result,
|
|
245
|
+
"step_name": step_name,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def create_step_failed_event(
|
|
251
|
+
run_id: str,
|
|
252
|
+
step_id: str,
|
|
253
|
+
error: str,
|
|
254
|
+
error_type: str,
|
|
255
|
+
is_retryable: bool,
|
|
256
|
+
attempt: int,
|
|
257
|
+
traceback: str | None = None,
|
|
258
|
+
) -> Event:
|
|
259
|
+
"""Create a step failed event."""
|
|
260
|
+
return Event(
|
|
261
|
+
run_id=run_id,
|
|
262
|
+
type=EventType.STEP_FAILED,
|
|
263
|
+
data={
|
|
264
|
+
"step_id": step_id,
|
|
265
|
+
"error": error,
|
|
266
|
+
"error_type": error_type,
|
|
267
|
+
"is_retryable": is_retryable,
|
|
268
|
+
"attempt": attempt,
|
|
269
|
+
"traceback": traceback,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def create_step_retrying_event(
|
|
275
|
+
run_id: str,
|
|
276
|
+
step_id: str,
|
|
277
|
+
attempt: int,
|
|
278
|
+
retry_after: str | None = None,
|
|
279
|
+
error: str | None = None,
|
|
280
|
+
) -> Event:
|
|
281
|
+
"""Create a step retrying event."""
|
|
282
|
+
return Event(
|
|
283
|
+
run_id=run_id,
|
|
284
|
+
type=EventType.STEP_RETRYING,
|
|
285
|
+
data={
|
|
286
|
+
"step_id": step_id,
|
|
287
|
+
"attempt": attempt,
|
|
288
|
+
"retry_after": retry_after,
|
|
289
|
+
"error": error,
|
|
290
|
+
},
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def create_sleep_started_event(
|
|
295
|
+
run_id: str,
|
|
296
|
+
sleep_id: str,
|
|
297
|
+
duration_seconds: int,
|
|
298
|
+
resume_at: datetime,
|
|
299
|
+
name: str | None = None,
|
|
300
|
+
) -> Event:
|
|
301
|
+
"""Create a sleep started event."""
|
|
302
|
+
return Event(
|
|
303
|
+
run_id=run_id,
|
|
304
|
+
type=EventType.SLEEP_STARTED,
|
|
305
|
+
data={
|
|
306
|
+
"sleep_id": sleep_id,
|
|
307
|
+
"duration_seconds": duration_seconds,
|
|
308
|
+
"resume_at": resume_at.isoformat(),
|
|
309
|
+
"name": name,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def create_sleep_completed_event(run_id: str, sleep_id: str) -> Event:
|
|
315
|
+
"""Create a sleep completed event."""
|
|
316
|
+
return Event(
|
|
317
|
+
run_id=run_id,
|
|
318
|
+
type=EventType.SLEEP_COMPLETED,
|
|
319
|
+
data={"sleep_id": sleep_id},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def create_hook_created_event(
|
|
324
|
+
run_id: str,
|
|
325
|
+
hook_id: str,
|
|
326
|
+
token: str = "",
|
|
327
|
+
url: str = "",
|
|
328
|
+
expires_at: datetime | None = None,
|
|
329
|
+
name: str | None = None,
|
|
330
|
+
hook_name: str | None = None,
|
|
331
|
+
timeout_seconds: int | None = None,
|
|
332
|
+
) -> Event:
|
|
333
|
+
"""
|
|
334
|
+
Create a hook created event.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
run_id: Workflow run ID
|
|
338
|
+
hook_id: Unique hook identifier
|
|
339
|
+
token: Security token for resuming the hook
|
|
340
|
+
url: Optional webhook URL
|
|
341
|
+
expires_at: Optional expiration datetime
|
|
342
|
+
name: Optional hook name (alias: hook_name)
|
|
343
|
+
hook_name: Alias for name (for backwards compatibility)
|
|
344
|
+
timeout_seconds: Alternative to expires_at (converted internally)
|
|
345
|
+
"""
|
|
346
|
+
# Handle aliases
|
|
347
|
+
actual_name = name or hook_name
|
|
348
|
+
|
|
349
|
+
# Convert timeout_seconds to expires_at if provided
|
|
350
|
+
actual_expires_at = expires_at
|
|
351
|
+
if timeout_seconds and not expires_at:
|
|
352
|
+
from datetime import UTC, timedelta
|
|
353
|
+
|
|
354
|
+
actual_expires_at = datetime.now(UTC) + timedelta(seconds=timeout_seconds)
|
|
355
|
+
|
|
356
|
+
return Event(
|
|
357
|
+
run_id=run_id,
|
|
358
|
+
type=EventType.HOOK_CREATED,
|
|
359
|
+
data={
|
|
360
|
+
"hook_id": hook_id,
|
|
361
|
+
"url": url,
|
|
362
|
+
"token": token,
|
|
363
|
+
"expires_at": actual_expires_at.isoformat() if actual_expires_at else None,
|
|
364
|
+
"name": actual_name,
|
|
365
|
+
},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def create_hook_received_event(run_id: str, hook_id: str, payload: Any) -> Event:
|
|
370
|
+
"""Create a hook received event."""
|
|
371
|
+
return Event(
|
|
372
|
+
run_id=run_id,
|
|
373
|
+
type=EventType.HOOK_RECEIVED,
|
|
374
|
+
data={
|
|
375
|
+
"hook_id": hook_id,
|
|
376
|
+
"payload": payload,
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def create_hook_expired_event(run_id: str, hook_id: str) -> Event:
|
|
382
|
+
"""Create a hook expired event."""
|
|
383
|
+
return Event(
|
|
384
|
+
run_id=run_id,
|
|
385
|
+
type=EventType.HOOK_EXPIRED,
|
|
386
|
+
data={"hook_id": hook_id},
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def create_cancellation_requested_event(
|
|
391
|
+
run_id: str,
|
|
392
|
+
reason: str | None = None,
|
|
393
|
+
requested_by: str | None = None,
|
|
394
|
+
) -> Event:
|
|
395
|
+
"""
|
|
396
|
+
Create a cancellation requested event.
|
|
397
|
+
|
|
398
|
+
This event is recorded when cancellation is requested for a workflow.
|
|
399
|
+
It signals that the workflow should terminate gracefully.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
run_id: The workflow run ID
|
|
403
|
+
reason: Optional reason for cancellation (e.g., "user_requested", "timeout")
|
|
404
|
+
requested_by: Optional identifier of who/what requested the cancellation
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Event: The cancellation requested event
|
|
408
|
+
"""
|
|
409
|
+
return Event(
|
|
410
|
+
run_id=run_id,
|
|
411
|
+
type=EventType.CANCELLATION_REQUESTED,
|
|
412
|
+
data={
|
|
413
|
+
"reason": reason,
|
|
414
|
+
"requested_by": requested_by,
|
|
415
|
+
"requested_at": datetime.now(UTC).isoformat(),
|
|
416
|
+
},
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def create_workflow_cancelled_event(
|
|
421
|
+
run_id: str,
|
|
422
|
+
reason: str | None = None,
|
|
423
|
+
cleanup_completed: bool = False,
|
|
424
|
+
) -> Event:
|
|
425
|
+
"""
|
|
426
|
+
Create a workflow cancelled event.
|
|
427
|
+
|
|
428
|
+
This event is recorded when a workflow has been successfully cancelled,
|
|
429
|
+
optionally after cleanup operations have completed.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
run_id: The workflow run ID
|
|
433
|
+
reason: Optional reason for cancellation
|
|
434
|
+
cleanup_completed: Whether cleanup operations completed successfully
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Event: The workflow cancelled event
|
|
438
|
+
"""
|
|
439
|
+
return Event(
|
|
440
|
+
run_id=run_id,
|
|
441
|
+
type=EventType.WORKFLOW_CANCELLED,
|
|
442
|
+
data={
|
|
443
|
+
"reason": reason,
|
|
444
|
+
"cleanup_completed": cleanup_completed,
|
|
445
|
+
"cancelled_at": datetime.now(UTC).isoformat(),
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def create_step_cancelled_event(
|
|
451
|
+
run_id: str,
|
|
452
|
+
step_id: str,
|
|
453
|
+
step_name: str,
|
|
454
|
+
reason: str | None = None,
|
|
455
|
+
) -> Event:
|
|
456
|
+
"""
|
|
457
|
+
Create a step cancelled event.
|
|
458
|
+
|
|
459
|
+
This event is recorded when a step is cancelled, either because the
|
|
460
|
+
workflow was cancelled or the step was explicitly terminated.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
run_id: The workflow run ID
|
|
464
|
+
step_id: The unique step identifier
|
|
465
|
+
step_name: The name of the step
|
|
466
|
+
reason: Optional reason for cancellation
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Event: The step cancelled event
|
|
470
|
+
"""
|
|
471
|
+
return Event(
|
|
472
|
+
run_id=run_id,
|
|
473
|
+
type=EventType.STEP_CANCELLED,
|
|
474
|
+
data={
|
|
475
|
+
"step_id": step_id,
|
|
476
|
+
"step_name": step_name,
|
|
477
|
+
"reason": reason,
|
|
478
|
+
"cancelled_at": datetime.now(UTC).isoformat(),
|
|
479
|
+
},
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# Child workflow event creation helpers
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def create_child_workflow_started_event(
|
|
487
|
+
run_id: str,
|
|
488
|
+
child_id: str,
|
|
489
|
+
child_run_id: str,
|
|
490
|
+
child_workflow_name: str,
|
|
491
|
+
args: Any,
|
|
492
|
+
kwargs: Any,
|
|
493
|
+
wait_for_completion: bool,
|
|
494
|
+
) -> Event:
|
|
495
|
+
"""
|
|
496
|
+
Create a child workflow started event.
|
|
497
|
+
|
|
498
|
+
This event is recorded in the parent workflow's event log when a child
|
|
499
|
+
workflow is spawned.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
run_id: The parent workflow run ID
|
|
503
|
+
child_id: Deterministic child identifier (for replay)
|
|
504
|
+
child_run_id: The child workflow's unique run ID
|
|
505
|
+
child_workflow_name: The name of the child workflow
|
|
506
|
+
args: Serialized positional arguments for child workflow
|
|
507
|
+
kwargs: Serialized keyword arguments for child workflow
|
|
508
|
+
wait_for_completion: Whether parent is waiting for child to complete
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Event: The child workflow started event
|
|
512
|
+
"""
|
|
513
|
+
return Event(
|
|
514
|
+
run_id=run_id,
|
|
515
|
+
type=EventType.CHILD_WORKFLOW_STARTED,
|
|
516
|
+
data={
|
|
517
|
+
"child_id": child_id,
|
|
518
|
+
"child_run_id": child_run_id,
|
|
519
|
+
"child_workflow_name": child_workflow_name,
|
|
520
|
+
"args": args,
|
|
521
|
+
"kwargs": kwargs,
|
|
522
|
+
"wait_for_completion": wait_for_completion,
|
|
523
|
+
"started_at": datetime.now(UTC).isoformat(),
|
|
524
|
+
},
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def create_child_workflow_completed_event(
|
|
529
|
+
run_id: str,
|
|
530
|
+
child_id: str,
|
|
531
|
+
child_run_id: str,
|
|
532
|
+
result: Any,
|
|
533
|
+
) -> Event:
|
|
534
|
+
"""
|
|
535
|
+
Create a child workflow completed event.
|
|
536
|
+
|
|
537
|
+
This event is recorded in the parent workflow's event log when a child
|
|
538
|
+
workflow completes successfully.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
run_id: The parent workflow run ID
|
|
542
|
+
child_id: Deterministic child identifier (for replay)
|
|
543
|
+
child_run_id: The child workflow's run ID
|
|
544
|
+
result: Serialized result from the child workflow
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Event: The child workflow completed event
|
|
548
|
+
"""
|
|
549
|
+
return Event(
|
|
550
|
+
run_id=run_id,
|
|
551
|
+
type=EventType.CHILD_WORKFLOW_COMPLETED,
|
|
552
|
+
data={
|
|
553
|
+
"child_id": child_id,
|
|
554
|
+
"child_run_id": child_run_id,
|
|
555
|
+
"result": result,
|
|
556
|
+
"completed_at": datetime.now(UTC).isoformat(),
|
|
557
|
+
},
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def create_child_workflow_failed_event(
|
|
562
|
+
run_id: str,
|
|
563
|
+
child_id: str,
|
|
564
|
+
child_run_id: str,
|
|
565
|
+
error: str,
|
|
566
|
+
error_type: str,
|
|
567
|
+
) -> Event:
|
|
568
|
+
"""
|
|
569
|
+
Create a child workflow failed event.
|
|
570
|
+
|
|
571
|
+
This event is recorded in the parent workflow's event log when a child
|
|
572
|
+
workflow fails.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
run_id: The parent workflow run ID
|
|
576
|
+
child_id: Deterministic child identifier (for replay)
|
|
577
|
+
child_run_id: The child workflow's run ID
|
|
578
|
+
error: Error message from the child workflow
|
|
579
|
+
error_type: The exception type that caused the failure
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Event: The child workflow failed event
|
|
583
|
+
"""
|
|
584
|
+
return Event(
|
|
585
|
+
run_id=run_id,
|
|
586
|
+
type=EventType.CHILD_WORKFLOW_FAILED,
|
|
587
|
+
data={
|
|
588
|
+
"child_id": child_id,
|
|
589
|
+
"child_run_id": child_run_id,
|
|
590
|
+
"error": error,
|
|
591
|
+
"error_type": error_type,
|
|
592
|
+
"failed_at": datetime.now(UTC).isoformat(),
|
|
593
|
+
},
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def create_child_workflow_cancelled_event(
|
|
598
|
+
run_id: str,
|
|
599
|
+
child_id: str,
|
|
600
|
+
child_run_id: str,
|
|
601
|
+
reason: str | None = None,
|
|
602
|
+
) -> Event:
|
|
603
|
+
"""
|
|
604
|
+
Create a child workflow cancelled event.
|
|
605
|
+
|
|
606
|
+
This event is recorded in the parent workflow's event log when a child
|
|
607
|
+
workflow is cancelled (typically due to parent completion or explicit cancel).
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
run_id: The parent workflow run ID
|
|
611
|
+
child_id: Deterministic child identifier (for replay)
|
|
612
|
+
child_run_id: The child workflow's run ID
|
|
613
|
+
reason: Optional reason for cancellation
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
Event: The child workflow cancelled event
|
|
617
|
+
"""
|
|
618
|
+
return Event(
|
|
619
|
+
run_id=run_id,
|
|
620
|
+
type=EventType.CHILD_WORKFLOW_CANCELLED,
|
|
621
|
+
data={
|
|
622
|
+
"child_id": child_id,
|
|
623
|
+
"child_run_id": child_run_id,
|
|
624
|
+
"reason": reason,
|
|
625
|
+
"cancelled_at": datetime.now(UTC).isoformat(),
|
|
626
|
+
},
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
# Schedule event creation helpers
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def create_schedule_created_event(
|
|
634
|
+
run_id: str,
|
|
635
|
+
schedule_id: str,
|
|
636
|
+
workflow_name: str,
|
|
637
|
+
spec: dict[str, Any],
|
|
638
|
+
overlap_policy: str,
|
|
639
|
+
) -> Event:
|
|
640
|
+
"""
|
|
641
|
+
Create a schedule created event.
|
|
642
|
+
|
|
643
|
+
This event is recorded when a new schedule is created.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
647
|
+
schedule_id: The schedule identifier
|
|
648
|
+
workflow_name: Name of the workflow being scheduled
|
|
649
|
+
spec: The schedule specification (as dict)
|
|
650
|
+
overlap_policy: The overlap policy for the schedule
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Event: The schedule created event
|
|
654
|
+
"""
|
|
655
|
+
return Event(
|
|
656
|
+
run_id=run_id,
|
|
657
|
+
type=EventType.SCHEDULE_CREATED,
|
|
658
|
+
data={
|
|
659
|
+
"schedule_id": schedule_id,
|
|
660
|
+
"workflow_name": workflow_name,
|
|
661
|
+
"spec": spec,
|
|
662
|
+
"overlap_policy": overlap_policy,
|
|
663
|
+
"created_at": datetime.now(UTC).isoformat(),
|
|
664
|
+
},
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def create_schedule_triggered_event(
|
|
669
|
+
run_id: str,
|
|
670
|
+
schedule_id: str,
|
|
671
|
+
scheduled_time: datetime,
|
|
672
|
+
actual_time: datetime,
|
|
673
|
+
workflow_run_id: str,
|
|
674
|
+
) -> Event:
|
|
675
|
+
"""
|
|
676
|
+
Create a schedule triggered event.
|
|
677
|
+
|
|
678
|
+
This event is recorded when a schedule triggers a workflow execution.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
run_id: The workflow run ID being created
|
|
682
|
+
schedule_id: The schedule identifier
|
|
683
|
+
scheduled_time: The time the schedule was supposed to trigger
|
|
684
|
+
actual_time: The actual time the trigger occurred
|
|
685
|
+
workflow_run_id: The ID of the workflow run being created
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
Event: The schedule triggered event
|
|
689
|
+
"""
|
|
690
|
+
return Event(
|
|
691
|
+
run_id=run_id,
|
|
692
|
+
type=EventType.SCHEDULE_TRIGGERED,
|
|
693
|
+
data={
|
|
694
|
+
"schedule_id": schedule_id,
|
|
695
|
+
"scheduled_time": scheduled_time.isoformat(),
|
|
696
|
+
"actual_time": actual_time.isoformat(),
|
|
697
|
+
"workflow_run_id": workflow_run_id,
|
|
698
|
+
},
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def create_schedule_skipped_event(
|
|
703
|
+
run_id: str,
|
|
704
|
+
schedule_id: str,
|
|
705
|
+
reason: str,
|
|
706
|
+
scheduled_time: datetime,
|
|
707
|
+
overlap_policy: str | None = None,
|
|
708
|
+
) -> Event:
|
|
709
|
+
"""
|
|
710
|
+
Create a schedule skipped event.
|
|
711
|
+
|
|
712
|
+
This event is recorded when a scheduled execution is skipped,
|
|
713
|
+
typically due to overlap policy or schedule being paused.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
717
|
+
schedule_id: The schedule identifier
|
|
718
|
+
reason: The reason for skipping
|
|
719
|
+
scheduled_time: The time the schedule was supposed to trigger
|
|
720
|
+
overlap_policy: The overlap policy that caused the skip
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
Event: The schedule skipped event
|
|
724
|
+
"""
|
|
725
|
+
return Event(
|
|
726
|
+
run_id=run_id,
|
|
727
|
+
type=EventType.SCHEDULE_SKIPPED,
|
|
728
|
+
data={
|
|
729
|
+
"schedule_id": schedule_id,
|
|
730
|
+
"reason": reason,
|
|
731
|
+
"scheduled_time": scheduled_time.isoformat(),
|
|
732
|
+
"overlap_policy": overlap_policy,
|
|
733
|
+
"skipped_at": datetime.now(UTC).isoformat(),
|
|
734
|
+
},
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def create_schedule_paused_event(
|
|
739
|
+
run_id: str,
|
|
740
|
+
schedule_id: str,
|
|
741
|
+
reason: str | None = None,
|
|
742
|
+
) -> Event:
|
|
743
|
+
"""
|
|
744
|
+
Create a schedule paused event.
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
748
|
+
schedule_id: The schedule identifier
|
|
749
|
+
reason: Optional reason for pausing
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
Event: The schedule paused event
|
|
753
|
+
"""
|
|
754
|
+
return Event(
|
|
755
|
+
run_id=run_id,
|
|
756
|
+
type=EventType.SCHEDULE_PAUSED,
|
|
757
|
+
data={
|
|
758
|
+
"schedule_id": schedule_id,
|
|
759
|
+
"reason": reason,
|
|
760
|
+
"paused_at": datetime.now(UTC).isoformat(),
|
|
761
|
+
},
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def create_schedule_resumed_event(
|
|
766
|
+
run_id: str,
|
|
767
|
+
schedule_id: str,
|
|
768
|
+
next_run_time: datetime | None = None,
|
|
769
|
+
) -> Event:
|
|
770
|
+
"""
|
|
771
|
+
Create a schedule resumed event.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
775
|
+
schedule_id: The schedule identifier
|
|
776
|
+
next_run_time: The next scheduled run time after resumption
|
|
777
|
+
|
|
778
|
+
Returns:
|
|
779
|
+
Event: The schedule resumed event
|
|
780
|
+
"""
|
|
781
|
+
return Event(
|
|
782
|
+
run_id=run_id,
|
|
783
|
+
type=EventType.SCHEDULE_RESUMED,
|
|
784
|
+
data={
|
|
785
|
+
"schedule_id": schedule_id,
|
|
786
|
+
"next_run_time": next_run_time.isoformat() if next_run_time else None,
|
|
787
|
+
"resumed_at": datetime.now(UTC).isoformat(),
|
|
788
|
+
},
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def create_schedule_deleted_event(
|
|
793
|
+
run_id: str,
|
|
794
|
+
schedule_id: str,
|
|
795
|
+
reason: str | None = None,
|
|
796
|
+
) -> Event:
|
|
797
|
+
"""
|
|
798
|
+
Create a schedule deleted event.
|
|
799
|
+
|
|
800
|
+
Args:
|
|
801
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
802
|
+
schedule_id: The schedule identifier
|
|
803
|
+
reason: Optional reason for deletion
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
Event: The schedule deleted event
|
|
807
|
+
"""
|
|
808
|
+
return Event(
|
|
809
|
+
run_id=run_id,
|
|
810
|
+
type=EventType.SCHEDULE_DELETED,
|
|
811
|
+
data={
|
|
812
|
+
"schedule_id": schedule_id,
|
|
813
|
+
"reason": reason,
|
|
814
|
+
"deleted_at": datetime.now(UTC).isoformat(),
|
|
815
|
+
},
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def create_schedule_backfill_started_event(
|
|
820
|
+
run_id: str,
|
|
821
|
+
schedule_id: str,
|
|
822
|
+
start_time: datetime,
|
|
823
|
+
end_time: datetime,
|
|
824
|
+
expected_runs: int,
|
|
825
|
+
) -> Event:
|
|
826
|
+
"""
|
|
827
|
+
Create a schedule backfill started event.
|
|
828
|
+
|
|
829
|
+
Args:
|
|
830
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
831
|
+
schedule_id: The schedule identifier
|
|
832
|
+
start_time: Start of the backfill period
|
|
833
|
+
end_time: End of the backfill period
|
|
834
|
+
expected_runs: Expected number of runs to create
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
Event: The schedule backfill started event
|
|
838
|
+
"""
|
|
839
|
+
return Event(
|
|
840
|
+
run_id=run_id,
|
|
841
|
+
type=EventType.SCHEDULE_BACKFILL_STARTED,
|
|
842
|
+
data={
|
|
843
|
+
"schedule_id": schedule_id,
|
|
844
|
+
"start_time": start_time.isoformat(),
|
|
845
|
+
"end_time": end_time.isoformat(),
|
|
846
|
+
"expected_runs": expected_runs,
|
|
847
|
+
"started_at": datetime.now(UTC).isoformat(),
|
|
848
|
+
},
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def create_schedule_backfill_completed_event(
|
|
853
|
+
run_id: str,
|
|
854
|
+
schedule_id: str,
|
|
855
|
+
runs_created: int,
|
|
856
|
+
run_ids: list[str],
|
|
857
|
+
) -> Event:
|
|
858
|
+
"""
|
|
859
|
+
Create a schedule backfill completed event.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
run_id: The run ID (use schedule_id for schedule-level events)
|
|
863
|
+
schedule_id: The schedule identifier
|
|
864
|
+
runs_created: Number of runs actually created
|
|
865
|
+
run_ids: List of created run IDs
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
Event: The schedule backfill completed event
|
|
869
|
+
"""
|
|
870
|
+
return Event(
|
|
871
|
+
run_id=run_id,
|
|
872
|
+
type=EventType.SCHEDULE_BACKFILL_COMPLETED,
|
|
873
|
+
data={
|
|
874
|
+
"schedule_id": schedule_id,
|
|
875
|
+
"runs_created": runs_created,
|
|
876
|
+
"run_ids": run_ids,
|
|
877
|
+
"completed_at": datetime.now(UTC).isoformat(),
|
|
878
|
+
},
|
|
879
|
+
)
|