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.
- pyworkflow/__init__.py +10 -1
- pyworkflow/celery/tasks.py +272 -24
- pyworkflow/cli/__init__.py +4 -1
- pyworkflow/cli/commands/runs.py +4 -4
- pyworkflow/cli/commands/setup.py +203 -4
- pyworkflow/cli/utils/config_generator.py +76 -3
- pyworkflow/cli/utils/docker_manager.py +232 -0
- pyworkflow/context/__init__.py +13 -0
- pyworkflow/context/base.py +26 -0
- pyworkflow/context/local.py +80 -0
- pyworkflow/context/step_context.py +295 -0
- pyworkflow/core/registry.py +6 -1
- pyworkflow/core/step.py +141 -0
- pyworkflow/core/workflow.py +56 -0
- pyworkflow/engine/events.py +30 -0
- pyworkflow/engine/replay.py +39 -0
- pyworkflow/primitives/child_workflow.py +1 -1
- pyworkflow/runtime/local.py +1 -1
- pyworkflow/storage/__init__.py +14 -0
- pyworkflow/storage/base.py +35 -0
- pyworkflow/storage/cassandra.py +1747 -0
- pyworkflow/storage/config.py +69 -0
- pyworkflow/storage/dynamodb.py +31 -2
- pyworkflow/storage/file.py +28 -0
- pyworkflow/storage/memory.py +18 -0
- pyworkflow/storage/mysql.py +1159 -0
- pyworkflow/storage/postgres.py +27 -2
- pyworkflow/storage/schemas.py +4 -3
- pyworkflow/storage/sqlite.py +25 -2
- {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/METADATA +7 -4
- pyworkflow_engine-0.1.9.dist-info/RECORD +91 -0
- pyworkflow_engine-0.1.9.dist-info/top_level.txt +1 -0
- dashboard/backend/app/__init__.py +0 -1
- dashboard/backend/app/config.py +0 -32
- dashboard/backend/app/controllers/__init__.py +0 -6
- dashboard/backend/app/controllers/run_controller.py +0 -86
- dashboard/backend/app/controllers/workflow_controller.py +0 -33
- dashboard/backend/app/dependencies/__init__.py +0 -5
- dashboard/backend/app/dependencies/storage.py +0 -50
- dashboard/backend/app/repositories/__init__.py +0 -6
- dashboard/backend/app/repositories/run_repository.py +0 -80
- dashboard/backend/app/repositories/workflow_repository.py +0 -27
- dashboard/backend/app/rest/__init__.py +0 -8
- dashboard/backend/app/rest/v1/__init__.py +0 -12
- dashboard/backend/app/rest/v1/health.py +0 -33
- dashboard/backend/app/rest/v1/runs.py +0 -133
- dashboard/backend/app/rest/v1/workflows.py +0 -41
- dashboard/backend/app/schemas/__init__.py +0 -23
- dashboard/backend/app/schemas/common.py +0 -16
- dashboard/backend/app/schemas/event.py +0 -24
- dashboard/backend/app/schemas/hook.py +0 -25
- dashboard/backend/app/schemas/run.py +0 -54
- dashboard/backend/app/schemas/step.py +0 -28
- dashboard/backend/app/schemas/workflow.py +0 -31
- dashboard/backend/app/server.py +0 -87
- dashboard/backend/app/services/__init__.py +0 -6
- dashboard/backend/app/services/run_service.py +0 -240
- dashboard/backend/app/services/workflow_service.py +0 -155
- dashboard/backend/main.py +0 -18
- docs/concepts/cancellation.mdx +0 -362
- docs/concepts/continue-as-new.mdx +0 -434
- docs/concepts/events.mdx +0 -266
- docs/concepts/fault-tolerance.mdx +0 -370
- docs/concepts/hooks.mdx +0 -552
- docs/concepts/limitations.mdx +0 -167
- docs/concepts/schedules.mdx +0 -775
- docs/concepts/sleep.mdx +0 -312
- docs/concepts/steps.mdx +0 -301
- docs/concepts/workflows.mdx +0 -255
- docs/guides/cli.mdx +0 -942
- docs/guides/configuration.mdx +0 -560
- docs/introduction.mdx +0 -155
- docs/quickstart.mdx +0 -279
- examples/__init__.py +0 -1
- examples/celery/__init__.py +0 -1
- examples/celery/durable/docker-compose.yml +0 -55
- examples/celery/durable/pyworkflow.config.yaml +0 -12
- examples/celery/durable/workflows/__init__.py +0 -122
- examples/celery/durable/workflows/basic.py +0 -87
- examples/celery/durable/workflows/batch_processing.py +0 -102
- examples/celery/durable/workflows/cancellation.py +0 -273
- examples/celery/durable/workflows/child_workflow_patterns.py +0 -240
- examples/celery/durable/workflows/child_workflows.py +0 -202
- examples/celery/durable/workflows/continue_as_new.py +0 -260
- examples/celery/durable/workflows/fault_tolerance.py +0 -210
- examples/celery/durable/workflows/hooks.py +0 -211
- examples/celery/durable/workflows/idempotency.py +0 -112
- examples/celery/durable/workflows/long_running.py +0 -99
- examples/celery/durable/workflows/retries.py +0 -101
- examples/celery/durable/workflows/schedules.py +0 -209
- examples/celery/transient/01_basic_workflow.py +0 -91
- examples/celery/transient/02_fault_tolerance.py +0 -257
- examples/celery/transient/__init__.py +0 -20
- examples/celery/transient/pyworkflow.config.yaml +0 -25
- examples/local/__init__.py +0 -1
- examples/local/durable/01_basic_workflow.py +0 -94
- examples/local/durable/02_file_storage.py +0 -132
- examples/local/durable/03_retries.py +0 -169
- examples/local/durable/04_long_running.py +0 -119
- examples/local/durable/05_event_log.py +0 -145
- examples/local/durable/06_idempotency.py +0 -148
- examples/local/durable/07_hooks.py +0 -334
- examples/local/durable/08_cancellation.py +0 -233
- examples/local/durable/09_child_workflows.py +0 -198
- examples/local/durable/10_child_workflow_patterns.py +0 -265
- examples/local/durable/11_continue_as_new.py +0 -249
- examples/local/durable/12_schedules.py +0 -198
- examples/local/durable/__init__.py +0 -1
- examples/local/transient/01_quick_tasks.py +0 -87
- examples/local/transient/02_retries.py +0 -130
- examples/local/transient/03_sleep.py +0 -141
- examples/local/transient/__init__.py +0 -1
- pyworkflow_engine-0.1.7.dist-info/RECORD +0 -196
- pyworkflow_engine-0.1.7.dist-info/top_level.txt +0 -5
- tests/examples/__init__.py +0 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_cancellation.py +0 -330
- tests/integration/test_child_workflows.py +0 -439
- tests/integration/test_continue_as_new.py +0 -428
- tests/integration/test_dynamodb_storage.py +0 -1146
- tests/integration/test_fault_tolerance.py +0 -369
- tests/integration/test_schedule_storage.py +0 -484
- tests/unit/__init__.py +0 -0
- tests/unit/backends/__init__.py +0 -1
- tests/unit/backends/test_dynamodb_storage.py +0 -1554
- tests/unit/backends/test_postgres_storage.py +0 -1281
- tests/unit/backends/test_sqlite_storage.py +0 -1460
- tests/unit/conftest.py +0 -41
- tests/unit/test_cancellation.py +0 -364
- tests/unit/test_child_workflows.py +0 -680
- tests/unit/test_continue_as_new.py +0 -441
- tests/unit/test_event_limits.py +0 -316
- tests/unit/test_executor.py +0 -320
- tests/unit/test_fault_tolerance.py +0 -334
- tests/unit/test_hooks.py +0 -495
- tests/unit/test_registry.py +0 -261
- tests/unit/test_replay.py +0 -420
- tests/unit/test_schedule_schemas.py +0 -285
- tests/unit/test_schedule_utils.py +0 -286
- tests/unit/test_scheduled_workflow.py +0 -274
- tests/unit/test_step.py +0 -353
- tests/unit/test_workflow.py +0 -243
- {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/WHEEL +0 -0
- {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/entry_points.txt +0 -0
- {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/licenses/LICENSE +0 -0
pyworkflow/storage/config.py
CHANGED
|
@@ -73,6 +73,32 @@ def storage_to_config(storage: StorageBackend | None) -> dict[str, Any] | None:
|
|
|
73
73
|
"region": getattr(storage, "region", "us-east-1"),
|
|
74
74
|
"endpoint_url": getattr(storage, "endpoint_url", None),
|
|
75
75
|
}
|
|
76
|
+
elif class_name == "CassandraStorageBackend":
|
|
77
|
+
return {
|
|
78
|
+
"type": "cassandra",
|
|
79
|
+
"contact_points": getattr(storage, "contact_points", ["localhost"]),
|
|
80
|
+
"port": getattr(storage, "port", 9042),
|
|
81
|
+
"keyspace": getattr(storage, "keyspace", "pyworkflow"),
|
|
82
|
+
"username": getattr(storage, "username", None),
|
|
83
|
+
"password": getattr(storage, "password", None),
|
|
84
|
+
"read_consistency": getattr(storage, "read_consistency", "LOCAL_QUORUM"),
|
|
85
|
+
"write_consistency": getattr(storage, "write_consistency", "LOCAL_QUORUM"),
|
|
86
|
+
"replication_strategy": getattr(storage, "replication_strategy", "SimpleStrategy"),
|
|
87
|
+
"replication_factor": getattr(storage, "replication_factor", 3),
|
|
88
|
+
"datacenter": getattr(storage, "datacenter", None),
|
|
89
|
+
}
|
|
90
|
+
elif class_name == "MySQLStorageBackend":
|
|
91
|
+
config = {"type": "mysql"}
|
|
92
|
+
dsn = getattr(storage, "dsn", None)
|
|
93
|
+
if dsn:
|
|
94
|
+
config["dsn"] = dsn
|
|
95
|
+
else:
|
|
96
|
+
config["host"] = getattr(storage, "host", "localhost")
|
|
97
|
+
config["port"] = getattr(storage, "port", 3306)
|
|
98
|
+
config["user"] = getattr(storage, "user", "pyworkflow")
|
|
99
|
+
config["password"] = getattr(storage, "password", "")
|
|
100
|
+
config["database"] = getattr(storage, "database", "pyworkflow")
|
|
101
|
+
return config
|
|
76
102
|
else:
|
|
77
103
|
# Unknown backend - return minimal config
|
|
78
104
|
return {"type": "unknown"}
|
|
@@ -181,5 +207,48 @@ def config_to_storage(config: dict[str, Any] | None = None) -> StorageBackend:
|
|
|
181
207
|
endpoint_url=config.get("endpoint_url"),
|
|
182
208
|
)
|
|
183
209
|
|
|
210
|
+
elif storage_type == "cassandra":
|
|
211
|
+
try:
|
|
212
|
+
from pyworkflow.storage.cassandra import CassandraStorageBackend
|
|
213
|
+
except ImportError:
|
|
214
|
+
raise ValueError(
|
|
215
|
+
"Cassandra storage backend is not available. "
|
|
216
|
+
"Please install the required dependencies with: pip install 'pyworkflow[cassandra]'"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return CassandraStorageBackend(
|
|
220
|
+
contact_points=config.get("contact_points", ["localhost"]),
|
|
221
|
+
port=config.get("port", 9042),
|
|
222
|
+
keyspace=config.get("keyspace", "pyworkflow"),
|
|
223
|
+
username=config.get("username"),
|
|
224
|
+
password=config.get("password"),
|
|
225
|
+
read_consistency=config.get("read_consistency", "LOCAL_QUORUM"),
|
|
226
|
+
write_consistency=config.get("write_consistency", "LOCAL_QUORUM"),
|
|
227
|
+
replication_strategy=config.get("replication_strategy", "SimpleStrategy"),
|
|
228
|
+
replication_factor=config.get("replication_factor", 3),
|
|
229
|
+
datacenter=config.get("datacenter"),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
elif storage_type == "mysql":
|
|
233
|
+
try:
|
|
234
|
+
from pyworkflow.storage.mysql import MySQLStorageBackend
|
|
235
|
+
except ImportError:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
"MySQL storage backend is not available. "
|
|
238
|
+
"Please install the required dependencies with: pip install 'pyworkflow[mysql]'"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Support both DSN and individual parameters
|
|
242
|
+
if "dsn" in config:
|
|
243
|
+
return MySQLStorageBackend(dsn=config["dsn"])
|
|
244
|
+
else:
|
|
245
|
+
return MySQLStorageBackend(
|
|
246
|
+
host=config.get("host", "localhost"),
|
|
247
|
+
port=config.get("port", 3306),
|
|
248
|
+
user=config.get("user", "pyworkflow"),
|
|
249
|
+
password=config.get("password", ""),
|
|
250
|
+
database=config.get("database", "pyworkflow"),
|
|
251
|
+
)
|
|
252
|
+
|
|
184
253
|
else:
|
|
185
254
|
raise ValueError(f"Unknown storage type: {storage_type}")
|
pyworkflow/storage/dynamodb.py
CHANGED
|
@@ -234,7 +234,7 @@ class DynamoDBStorageBackend(StorageBackend):
|
|
|
234
234
|
"error": run.error,
|
|
235
235
|
"idempotency_key": run.idempotency_key,
|
|
236
236
|
"max_duration": run.max_duration,
|
|
237
|
-
"
|
|
237
|
+
"context": json.dumps(run.context),
|
|
238
238
|
"recovery_attempts": run.recovery_attempts,
|
|
239
239
|
"max_recovery_attempts": run.max_recovery_attempts,
|
|
240
240
|
"recover_on_worker_loss": run.recover_on_worker_loss,
|
|
@@ -362,6 +362,34 @@ class DynamoDBStorageBackend(StorageBackend):
|
|
|
362
362
|
},
|
|
363
363
|
)
|
|
364
364
|
|
|
365
|
+
async def update_run_context(
|
|
366
|
+
self,
|
|
367
|
+
run_id: str,
|
|
368
|
+
context: dict,
|
|
369
|
+
) -> None:
|
|
370
|
+
"""Update the step context for a workflow run."""
|
|
371
|
+
async with self._get_client() as client:
|
|
372
|
+
await client.update_item(
|
|
373
|
+
TableName=self.table_name,
|
|
374
|
+
Key={
|
|
375
|
+
"PK": {"S": f"RUN#{run_id}"},
|
|
376
|
+
"SK": {"S": "#METADATA"},
|
|
377
|
+
},
|
|
378
|
+
UpdateExpression="SET #ctx = :ctx, updated_at = :now",
|
|
379
|
+
ExpressionAttributeNames={
|
|
380
|
+
"#ctx": "context",
|
|
381
|
+
},
|
|
382
|
+
ExpressionAttributeValues={
|
|
383
|
+
":ctx": {"S": json.dumps(context)},
|
|
384
|
+
":now": {"S": datetime.now(UTC).isoformat()},
|
|
385
|
+
},
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
async def get_run_context(self, run_id: str) -> dict:
|
|
389
|
+
"""Get the current step context for a workflow run."""
|
|
390
|
+
run = await self.get_run(run_id)
|
|
391
|
+
return run.context if run else {}
|
|
392
|
+
|
|
365
393
|
async def list_runs(
|
|
366
394
|
self,
|
|
367
395
|
query: str | None = None,
|
|
@@ -1221,7 +1249,8 @@ class DynamoDBStorageBackend(StorageBackend):
|
|
|
1221
1249
|
error=item.get("error"),
|
|
1222
1250
|
idempotency_key=item.get("idempotency_key"),
|
|
1223
1251
|
max_duration=item.get("max_duration"),
|
|
1224
|
-
|
|
1252
|
+
# Support both 'context' (new) and 'metadata' (legacy) keys
|
|
1253
|
+
context=json.loads(item.get("context", item.get("metadata", "{}"))),
|
|
1225
1254
|
recovery_attempts=item.get("recovery_attempts", 0),
|
|
1226
1255
|
max_recovery_attempts=item.get("max_recovery_attempts", 3),
|
|
1227
1256
|
recover_on_worker_loss=item.get("recover_on_worker_loss", True),
|
pyworkflow/storage/file.py
CHANGED
|
@@ -190,6 +190,34 @@ class FileStorageBackend(StorageBackend):
|
|
|
190
190
|
|
|
191
191
|
await asyncio.to_thread(_update)
|
|
192
192
|
|
|
193
|
+
async def update_run_context(
|
|
194
|
+
self,
|
|
195
|
+
run_id: str,
|
|
196
|
+
context: dict,
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Update the step context for a workflow run."""
|
|
199
|
+
run_file = self.runs_dir / f"{run_id}.json"
|
|
200
|
+
|
|
201
|
+
if not run_file.exists():
|
|
202
|
+
raise ValueError(f"Workflow run {run_id} not found")
|
|
203
|
+
|
|
204
|
+
lock_file = self.locks_dir / f"{run_id}.lock"
|
|
205
|
+
lock = FileLock(str(lock_file))
|
|
206
|
+
|
|
207
|
+
def _update() -> None:
|
|
208
|
+
with lock:
|
|
209
|
+
data = json.loads(run_file.read_text())
|
|
210
|
+
data["context"] = context
|
|
211
|
+
data["updated_at"] = datetime.now(UTC).isoformat()
|
|
212
|
+
run_file.write_text(json.dumps(data, indent=2))
|
|
213
|
+
|
|
214
|
+
await asyncio.to_thread(_update)
|
|
215
|
+
|
|
216
|
+
async def get_run_context(self, run_id: str) -> dict:
|
|
217
|
+
"""Get the current step context for a workflow run."""
|
|
218
|
+
run = await self.get_run(run_id)
|
|
219
|
+
return run.context if run else {}
|
|
220
|
+
|
|
193
221
|
async def list_runs(
|
|
194
222
|
self,
|
|
195
223
|
query: str | None = None,
|
pyworkflow/storage/memory.py
CHANGED
|
@@ -109,6 +109,24 @@ class InMemoryStorageBackend(StorageBackend):
|
|
|
109
109
|
run.recovery_attempts = recovery_attempts
|
|
110
110
|
run.updated_at = datetime.now(UTC)
|
|
111
111
|
|
|
112
|
+
async def update_run_context(
|
|
113
|
+
self,
|
|
114
|
+
run_id: str,
|
|
115
|
+
context: dict,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Update the step context for a workflow run."""
|
|
118
|
+
with self._lock:
|
|
119
|
+
run = self._runs.get(run_id)
|
|
120
|
+
if run:
|
|
121
|
+
run.context = context
|
|
122
|
+
run.updated_at = datetime.now(UTC)
|
|
123
|
+
|
|
124
|
+
async def get_run_context(self, run_id: str) -> dict:
|
|
125
|
+
"""Get the current step context for a workflow run."""
|
|
126
|
+
with self._lock:
|
|
127
|
+
run = self._runs.get(run_id)
|
|
128
|
+
return run.context if run else {}
|
|
129
|
+
|
|
112
130
|
async def list_runs(
|
|
113
131
|
self,
|
|
114
132
|
query: str | None = None,
|