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,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@scheduled_workflow decorator for defining workflows with built-in scheduling.
|
|
3
|
+
|
|
4
|
+
This decorator combines the @workflow decorator with schedule configuration,
|
|
5
|
+
enabling workflows to be automatically scheduled based on cron expressions,
|
|
6
|
+
intervals, or calendar specifications.
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
# Cron-based schedule (every day at 9 AM)
|
|
10
|
+
@scheduled_workflow(cron="0 9 * * *")
|
|
11
|
+
async def daily_report():
|
|
12
|
+
return await generate_report()
|
|
13
|
+
|
|
14
|
+
# Interval-based schedule (every 5 minutes)
|
|
15
|
+
@scheduled_workflow(interval="5m")
|
|
16
|
+
async def health_check():
|
|
17
|
+
return await check_system_health()
|
|
18
|
+
|
|
19
|
+
# Combined with workflow options
|
|
20
|
+
@scheduled_workflow(
|
|
21
|
+
cron="0 0 * * 0",
|
|
22
|
+
name="weekly_cleanup",
|
|
23
|
+
overlap_policy=OverlapPolicy.SKIP,
|
|
24
|
+
timezone="America/New_York",
|
|
25
|
+
)
|
|
26
|
+
async def weekly_cleanup():
|
|
27
|
+
return await cleanup_old_data()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import functools
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
from datetime import datetime
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from pyworkflow.core.registry import register_workflow
|
|
37
|
+
from pyworkflow.storage.schemas import CalendarSpec, OverlapPolicy, ScheduleSpec
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ScheduledWorkflowMetadata:
|
|
42
|
+
"""Metadata for a scheduled workflow."""
|
|
43
|
+
|
|
44
|
+
workflow_name: str
|
|
45
|
+
spec: ScheduleSpec
|
|
46
|
+
overlap_policy: OverlapPolicy
|
|
47
|
+
func: Callable[..., Any]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Registry for scheduled workflows (separate from main workflow registry)
|
|
51
|
+
_scheduled_workflows: dict[str, ScheduledWorkflowMetadata] = {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def scheduled_workflow(
|
|
55
|
+
cron: str | None = None,
|
|
56
|
+
interval: str | None = None,
|
|
57
|
+
calendar: list[CalendarSpec] | None = None,
|
|
58
|
+
timezone: str = "UTC",
|
|
59
|
+
start_at: datetime | None = None,
|
|
60
|
+
end_at: datetime | None = None,
|
|
61
|
+
jitter: str | None = None,
|
|
62
|
+
overlap_policy: OverlapPolicy = OverlapPolicy.SKIP,
|
|
63
|
+
name: str | None = None,
|
|
64
|
+
durable: bool | None = None,
|
|
65
|
+
max_duration: str | None = None,
|
|
66
|
+
recover_on_worker_loss: bool | None = None,
|
|
67
|
+
max_recovery_attempts: int | None = None,
|
|
68
|
+
) -> Callable:
|
|
69
|
+
"""
|
|
70
|
+
Decorator to define a workflow with built-in scheduling configuration.
|
|
71
|
+
|
|
72
|
+
This decorator combines @workflow functionality with schedule specification,
|
|
73
|
+
allowing the workflow to be automatically scheduled when registered with
|
|
74
|
+
a scheduler.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
cron: Cron expression for scheduling (e.g., "0 9 * * *" for daily at 9 AM)
|
|
78
|
+
interval: Interval duration (e.g., "5m", "1h", "30s")
|
|
79
|
+
calendar: List of CalendarSpec for calendar-based scheduling
|
|
80
|
+
timezone: Timezone for schedule (default: "UTC")
|
|
81
|
+
start_at: Optional start time for the schedule
|
|
82
|
+
end_at: Optional end time for the schedule
|
|
83
|
+
jitter: Random delay to prevent thundering herd (e.g., "30s")
|
|
84
|
+
overlap_policy: How to handle overlapping runs (default: SKIP)
|
|
85
|
+
name: Optional workflow name (defaults to function name)
|
|
86
|
+
durable: Whether workflow is durable (None = use configured default)
|
|
87
|
+
max_duration: Optional max duration (e.g., "1h", "30m")
|
|
88
|
+
recover_on_worker_loss: Whether to auto-recover on worker failure
|
|
89
|
+
max_recovery_attempts: Max recovery attempts on worker failure
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Decorated workflow function with schedule metadata
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If no schedule specification provided
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
# Every day at 9 AM
|
|
99
|
+
@scheduled_workflow(cron="0 9 * * *")
|
|
100
|
+
async def daily_report():
|
|
101
|
+
return await generate_report()
|
|
102
|
+
|
|
103
|
+
# Every 5 minutes with skip policy
|
|
104
|
+
@scheduled_workflow(
|
|
105
|
+
interval="5m",
|
|
106
|
+
overlap_policy=OverlapPolicy.SKIP,
|
|
107
|
+
)
|
|
108
|
+
async def health_check():
|
|
109
|
+
return await check_system_health()
|
|
110
|
+
|
|
111
|
+
# First of every month at midnight UTC
|
|
112
|
+
@scheduled_workflow(
|
|
113
|
+
calendar=[CalendarSpec(day_of_month=1, hour=0, minute=0)],
|
|
114
|
+
timezone="UTC",
|
|
115
|
+
)
|
|
116
|
+
async def monthly_billing():
|
|
117
|
+
return await process_billing()
|
|
118
|
+
|
|
119
|
+
# Complex schedule with workflow options
|
|
120
|
+
@scheduled_workflow(
|
|
121
|
+
cron="0 */4 * * *", # Every 4 hours
|
|
122
|
+
name="sync_data",
|
|
123
|
+
overlap_policy=OverlapPolicy.BUFFER_ONE,
|
|
124
|
+
recover_on_worker_loss=True,
|
|
125
|
+
max_recovery_attempts=5,
|
|
126
|
+
)
|
|
127
|
+
async def sync_external_data():
|
|
128
|
+
return await sync_data()
|
|
129
|
+
"""
|
|
130
|
+
# Validate at least one schedule type is provided
|
|
131
|
+
if not cron and not interval and not calendar:
|
|
132
|
+
raise ValueError("scheduled_workflow requires at least one of: cron, interval, or calendar")
|
|
133
|
+
|
|
134
|
+
def decorator(func: Callable) -> Callable:
|
|
135
|
+
workflow_name = name or func.__name__
|
|
136
|
+
|
|
137
|
+
@functools.wraps(func)
|
|
138
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
139
|
+
return await func(*args, **kwargs)
|
|
140
|
+
|
|
141
|
+
# Register as a regular workflow first
|
|
142
|
+
register_workflow(
|
|
143
|
+
name=workflow_name,
|
|
144
|
+
func=wrapper,
|
|
145
|
+
original_func=func,
|
|
146
|
+
max_duration=max_duration,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Store standard workflow metadata on wrapper
|
|
150
|
+
wrapper.__workflow__ = True # type: ignore[attr-defined]
|
|
151
|
+
wrapper.__workflow_name__ = workflow_name # type: ignore[attr-defined]
|
|
152
|
+
wrapper.__workflow_durable__ = durable # type: ignore[attr-defined]
|
|
153
|
+
wrapper.__workflow_max_duration__ = max_duration # type: ignore[attr-defined]
|
|
154
|
+
wrapper.__workflow_recover_on_worker_loss__ = recover_on_worker_loss # type: ignore[attr-defined]
|
|
155
|
+
wrapper.__workflow_max_recovery_attempts__ = max_recovery_attempts # type: ignore[attr-defined]
|
|
156
|
+
|
|
157
|
+
# Create schedule spec
|
|
158
|
+
spec = ScheduleSpec(
|
|
159
|
+
cron=cron,
|
|
160
|
+
interval=interval,
|
|
161
|
+
calendar=calendar,
|
|
162
|
+
timezone=timezone,
|
|
163
|
+
start_at=start_at,
|
|
164
|
+
end_at=end_at,
|
|
165
|
+
jitter=jitter,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Store schedule metadata on wrapper
|
|
169
|
+
wrapper.__scheduled__ = True # type: ignore[attr-defined]
|
|
170
|
+
wrapper.__schedule_spec__ = spec # type: ignore[attr-defined]
|
|
171
|
+
wrapper.__overlap_policy__ = overlap_policy # type: ignore[attr-defined]
|
|
172
|
+
|
|
173
|
+
# Register in scheduled workflows registry
|
|
174
|
+
scheduled_meta = ScheduledWorkflowMetadata(
|
|
175
|
+
workflow_name=workflow_name,
|
|
176
|
+
spec=spec,
|
|
177
|
+
overlap_policy=overlap_policy,
|
|
178
|
+
func=wrapper,
|
|
179
|
+
)
|
|
180
|
+
_scheduled_workflows[workflow_name] = scheduled_meta
|
|
181
|
+
|
|
182
|
+
return wrapper
|
|
183
|
+
|
|
184
|
+
return decorator
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_scheduled_workflow(name: str) -> ScheduledWorkflowMetadata | None:
|
|
188
|
+
"""
|
|
189
|
+
Get scheduled workflow metadata by name.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
name: Workflow name
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
ScheduledWorkflowMetadata if found, None otherwise
|
|
196
|
+
"""
|
|
197
|
+
return _scheduled_workflows.get(name)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def list_scheduled_workflows() -> dict[str, ScheduledWorkflowMetadata]:
|
|
201
|
+
"""
|
|
202
|
+
List all registered scheduled workflows.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dictionary mapping workflow names to their schedule metadata
|
|
206
|
+
"""
|
|
207
|
+
return _scheduled_workflows.copy()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def register_scheduled_workflow(
|
|
211
|
+
workflow_name: str,
|
|
212
|
+
spec: ScheduleSpec,
|
|
213
|
+
overlap_policy: OverlapPolicy,
|
|
214
|
+
func: Callable[..., Any],
|
|
215
|
+
) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Manually register a scheduled workflow.
|
|
218
|
+
|
|
219
|
+
This is useful when you want to add scheduling to an existing workflow
|
|
220
|
+
without using the @scheduled_workflow decorator.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
workflow_name: Name of the workflow to schedule
|
|
224
|
+
spec: Schedule specification
|
|
225
|
+
overlap_policy: How to handle overlapping runs
|
|
226
|
+
func: The workflow function
|
|
227
|
+
|
|
228
|
+
Examples:
|
|
229
|
+
from pyworkflow import workflow
|
|
230
|
+
from pyworkflow.core.scheduled import register_scheduled_workflow
|
|
231
|
+
|
|
232
|
+
@workflow
|
|
233
|
+
async def my_workflow():
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
# Add scheduling later
|
|
237
|
+
register_scheduled_workflow(
|
|
238
|
+
"my_workflow",
|
|
239
|
+
ScheduleSpec(cron="0 9 * * *"),
|
|
240
|
+
OverlapPolicy.SKIP,
|
|
241
|
+
my_workflow,
|
|
242
|
+
)
|
|
243
|
+
"""
|
|
244
|
+
scheduled_meta = ScheduledWorkflowMetadata(
|
|
245
|
+
workflow_name=workflow_name,
|
|
246
|
+
spec=spec,
|
|
247
|
+
overlap_policy=overlap_policy,
|
|
248
|
+
func=func,
|
|
249
|
+
)
|
|
250
|
+
_scheduled_workflows[workflow_name] = scheduled_meta
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def unregister_scheduled_workflow(workflow_name: str) -> bool:
|
|
254
|
+
"""
|
|
255
|
+
Unregister a scheduled workflow.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
workflow_name: Name of the workflow to unregister
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
True if workflow was unregistered, False if not found
|
|
262
|
+
"""
|
|
263
|
+
if workflow_name in _scheduled_workflows:
|
|
264
|
+
del _scheduled_workflows[workflow_name]
|
|
265
|
+
return True
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def clear_scheduled_workflows() -> None:
|
|
270
|
+
"""Clear all scheduled workflow registrations (useful for testing)."""
|
|
271
|
+
_scheduled_workflows.clear()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def activate_scheduled_workflows(
|
|
275
|
+
storage: Any = None,
|
|
276
|
+
schedule_id_prefix: str = "auto_",
|
|
277
|
+
) -> list[str]:
|
|
278
|
+
"""
|
|
279
|
+
Activate all registered scheduled workflows by creating schedules in storage.
|
|
280
|
+
|
|
281
|
+
This function takes all workflows decorated with @scheduled_workflow and
|
|
282
|
+
creates corresponding schedules in the storage backend.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
storage: Storage backend (uses global config if not provided)
|
|
286
|
+
schedule_id_prefix: Prefix for generated schedule IDs
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
List of created schedule IDs
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
from pyworkflow.core.scheduled import activate_scheduled_workflows
|
|
293
|
+
|
|
294
|
+
# Activate all @scheduled_workflow decorated functions
|
|
295
|
+
schedule_ids = await activate_scheduled_workflows()
|
|
296
|
+
print(f"Created {len(schedule_ids)} schedules")
|
|
297
|
+
"""
|
|
298
|
+
if storage is None:
|
|
299
|
+
from pyworkflow.config import get_config
|
|
300
|
+
|
|
301
|
+
storage = get_config().storage
|
|
302
|
+
|
|
303
|
+
if storage is None:
|
|
304
|
+
raise ValueError("Storage backend required to activate scheduled workflows")
|
|
305
|
+
|
|
306
|
+
from pyworkflow.primitives.schedule import create_schedule
|
|
307
|
+
|
|
308
|
+
created_ids: list[str] = []
|
|
309
|
+
|
|
310
|
+
for workflow_name, meta in _scheduled_workflows.items():
|
|
311
|
+
schedule_id = f"{schedule_id_prefix}{workflow_name}"
|
|
312
|
+
|
|
313
|
+
# Check if schedule already exists
|
|
314
|
+
existing = await storage.get_schedule(schedule_id)
|
|
315
|
+
if existing:
|
|
316
|
+
# Skip if already exists
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
schedule = await create_schedule(
|
|
320
|
+
workflow_name=workflow_name,
|
|
321
|
+
spec=meta.spec,
|
|
322
|
+
overlap_policy=meta.overlap_policy,
|
|
323
|
+
schedule_id=schedule_id,
|
|
324
|
+
storage=storage,
|
|
325
|
+
)
|
|
326
|
+
created_ids.append(schedule.schedule_id)
|
|
327
|
+
|
|
328
|
+
return created_ids
|