pyworkflow-engine 0.1.7__py3-none-any.whl → 0.1.10__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/config.py +94 -17
- 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.10.dist-info}/METADATA +7 -4
- pyworkflow_engine-0.1.10.dist-info/RECORD +91 -0
- pyworkflow_engine-0.1.10.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.10.dist-info}/WHEEL +0 -0
- {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.10.dist-info}/entry_points.txt +0 -0
- {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.10.dist-info}/licenses/LICENSE +0 -0
pyworkflow/cli/commands/setup.py
CHANGED
|
@@ -21,7 +21,9 @@ from pyworkflow.cli.utils.config_generator import (
|
|
|
21
21
|
from pyworkflow.cli.utils.docker_manager import (
|
|
22
22
|
check_docker_available,
|
|
23
23
|
check_service_health,
|
|
24
|
+
generate_cassandra_docker_compose_content,
|
|
24
25
|
generate_docker_compose_content,
|
|
26
|
+
generate_mysql_docker_compose_content,
|
|
25
27
|
generate_postgres_docker_compose_content,
|
|
26
28
|
run_docker_command,
|
|
27
29
|
write_docker_compose,
|
|
@@ -87,7 +89,10 @@ def _flatten_yaml_config(nested_config: dict) -> dict:
|
|
|
87
89
|
)
|
|
88
90
|
@click.option(
|
|
89
91
|
"--storage",
|
|
90
|
-
type=click.Choice(
|
|
92
|
+
type=click.Choice(
|
|
93
|
+
["file", "memory", "sqlite", "postgres", "mysql", "dynamodb", "cassandra"],
|
|
94
|
+
case_sensitive=False,
|
|
95
|
+
),
|
|
91
96
|
help="Storage backend type",
|
|
92
97
|
)
|
|
93
98
|
@click.option(
|
|
@@ -140,7 +145,7 @@ def setup(
|
|
|
140
145
|
sys.exit(1)
|
|
141
146
|
except Exception as e:
|
|
142
147
|
print_error(f"\nSetup failed: {str(e)}")
|
|
143
|
-
if ctx.obj.get("verbose"):
|
|
148
|
+
if ctx.obj and ctx.obj.get("verbose"):
|
|
144
149
|
raise
|
|
145
150
|
sys.exit(1)
|
|
146
151
|
|
|
@@ -239,6 +244,13 @@ def _run_setup(
|
|
|
239
244
|
|
|
240
245
|
# 6. Write configuration file
|
|
241
246
|
print_info("\nGenerating configuration...")
|
|
247
|
+
|
|
248
|
+
# Parse Cassandra contact points from comma-separated string to list
|
|
249
|
+
cassandra_contact_points = None
|
|
250
|
+
if config_data.get("cassandra_contact_points"):
|
|
251
|
+
contact_points_str = config_data["cassandra_contact_points"]
|
|
252
|
+
cassandra_contact_points = [cp.strip() for cp in contact_points_str.split(",")]
|
|
253
|
+
|
|
242
254
|
yaml_content = generate_yaml_config(
|
|
243
255
|
module=config_data.get("module"),
|
|
244
256
|
runtime=config_data["runtime"],
|
|
@@ -251,9 +263,19 @@ def _run_setup(
|
|
|
251
263
|
postgres_user=config_data.get("postgres_user"),
|
|
252
264
|
postgres_password=config_data.get("postgres_password"),
|
|
253
265
|
postgres_database=config_data.get("postgres_database"),
|
|
266
|
+
mysql_host=config_data.get("mysql_host"),
|
|
267
|
+
mysql_port=config_data.get("mysql_port"),
|
|
268
|
+
mysql_user=config_data.get("mysql_user"),
|
|
269
|
+
mysql_password=config_data.get("mysql_password"),
|
|
270
|
+
mysql_database=config_data.get("mysql_database"),
|
|
254
271
|
dynamodb_table_name=config_data.get("dynamodb_table_name"),
|
|
255
272
|
dynamodb_region=config_data.get("dynamodb_region"),
|
|
256
273
|
dynamodb_endpoint_url=config_data.get("dynamodb_endpoint_url"),
|
|
274
|
+
cassandra_contact_points=cassandra_contact_points,
|
|
275
|
+
cassandra_port=config_data.get("cassandra_port"),
|
|
276
|
+
cassandra_keyspace=config_data.get("cassandra_keyspace"),
|
|
277
|
+
cassandra_user=config_data.get("cassandra_user"),
|
|
278
|
+
cassandra_password=config_data.get("cassandra_password"),
|
|
257
279
|
)
|
|
258
280
|
|
|
259
281
|
config_file_path = write_yaml_config(yaml_content, config_path, backup=True)
|
|
@@ -313,6 +335,36 @@ def _check_postgres_available() -> bool:
|
|
|
313
335
|
return False
|
|
314
336
|
|
|
315
337
|
|
|
338
|
+
def _check_cassandra_available() -> bool:
|
|
339
|
+
"""
|
|
340
|
+
Check if cassandra-driver is installed for Cassandra support.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
True if cassandra-driver is available, False otherwise
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
from cassandra.cluster import Cluster # noqa: F401
|
|
347
|
+
|
|
348
|
+
return True
|
|
349
|
+
except ImportError:
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _check_mysql_available() -> bool:
|
|
354
|
+
"""
|
|
355
|
+
Check if aiomysql is installed for MySQL support.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
True if aiomysql is available, False otherwise
|
|
359
|
+
"""
|
|
360
|
+
try:
|
|
361
|
+
import aiomysql # noqa: F401
|
|
362
|
+
|
|
363
|
+
return True
|
|
364
|
+
except ImportError:
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
|
|
316
368
|
def _run_interactive_configuration(
|
|
317
369
|
non_interactive: bool,
|
|
318
370
|
module_override: str | None,
|
|
@@ -346,9 +398,11 @@ def _run_interactive_configuration(
|
|
|
346
398
|
config_data["result_backend"] = "redis://localhost:6379/1"
|
|
347
399
|
print_info("✓ Broker: Redis (will be started via Docker)")
|
|
348
400
|
|
|
349
|
-
# Check if SQLite and
|
|
401
|
+
# Check if SQLite, PostgreSQL, Cassandra and MySQL are available
|
|
350
402
|
sqlite_available = _check_sqlite_available()
|
|
351
403
|
postgres_available = _check_postgres_available()
|
|
404
|
+
cassandra_available = _check_cassandra_available()
|
|
405
|
+
mysql_available = _check_mysql_available()
|
|
352
406
|
|
|
353
407
|
# Storage backend
|
|
354
408
|
if storage_override:
|
|
@@ -378,6 +432,27 @@ def _run_interactive_configuration(
|
|
|
378
432
|
print_info("")
|
|
379
433
|
print_info("Or choose a different storage backend: --storage sqlite")
|
|
380
434
|
raise click.Abort()
|
|
435
|
+
# Validate if cassandra was requested but not available
|
|
436
|
+
if storage_type == "cassandra" and not cassandra_available:
|
|
437
|
+
print_error("\nCassandra storage backend is not available!")
|
|
438
|
+
print_info("\ncassandra-driver package is not installed.")
|
|
439
|
+
print_info("To fix this, install cassandra-driver:")
|
|
440
|
+
print_info("")
|
|
441
|
+
print_info(" pip install cassandra-driver")
|
|
442
|
+
print_info("")
|
|
443
|
+
print_info("Or choose a different storage backend: --storage sqlite")
|
|
444
|
+
raise click.Abort()
|
|
445
|
+
# Validate if mysql was requested but not available
|
|
446
|
+
mysql_available = _check_mysql_available()
|
|
447
|
+
if storage_type == "mysql" and not mysql_available:
|
|
448
|
+
print_error("\nMySQL storage backend is not available!")
|
|
449
|
+
print_info("\naiomysql package is not installed.")
|
|
450
|
+
print_info("To fix this, install aiomysql:")
|
|
451
|
+
print_info("")
|
|
452
|
+
print_info(" pip install aiomysql")
|
|
453
|
+
print_info("")
|
|
454
|
+
print_info("Or choose a different storage backend: --storage sqlite")
|
|
455
|
+
raise click.Abort()
|
|
381
456
|
elif non_interactive:
|
|
382
457
|
if sqlite_available:
|
|
383
458
|
storage_type = "sqlite"
|
|
@@ -407,6 +482,14 @@ def _run_interactive_configuration(
|
|
|
407
482
|
choices.append(
|
|
408
483
|
{"name": "PostgreSQL - Scalable production database", "value": "postgres"}
|
|
409
484
|
)
|
|
485
|
+
if cassandra_available:
|
|
486
|
+
choices.append(
|
|
487
|
+
{"name": "Cassandra - Distributed NoSQL database (scalable)", "value": "cassandra"}
|
|
488
|
+
)
|
|
489
|
+
if mysql_available:
|
|
490
|
+
choices.append(
|
|
491
|
+
{"name": "MySQL - Popular open-source relational database", "value": "mysql"}
|
|
492
|
+
)
|
|
410
493
|
choices.extend(
|
|
411
494
|
[
|
|
412
495
|
{
|
|
@@ -428,6 +511,14 @@ def _run_interactive_configuration(
|
|
|
428
511
|
print_info("Note: PostgreSQL backend available after: pip install asyncpg")
|
|
429
512
|
print_info("")
|
|
430
513
|
|
|
514
|
+
if not cassandra_available:
|
|
515
|
+
print_info("Note: Cassandra backend available after: pip install cassandra-driver")
|
|
516
|
+
print_info("")
|
|
517
|
+
|
|
518
|
+
if not mysql_available:
|
|
519
|
+
print_info("Note: MySQL backend available after: pip install aiomysql")
|
|
520
|
+
print_info("")
|
|
521
|
+
|
|
431
522
|
storage_type = select(
|
|
432
523
|
"Choose storage backend:",
|
|
433
524
|
choices=choices,
|
|
@@ -491,6 +582,38 @@ def _run_interactive_configuration(
|
|
|
491
582
|
default="pyworkflow",
|
|
492
583
|
)
|
|
493
584
|
|
|
585
|
+
# MySQL connection (for mysql backend)
|
|
586
|
+
elif storage_type == "mysql":
|
|
587
|
+
if non_interactive:
|
|
588
|
+
# Use default connection settings for non-interactive mode
|
|
589
|
+
config_data["mysql_host"] = "localhost"
|
|
590
|
+
config_data["mysql_port"] = "3306"
|
|
591
|
+
config_data["mysql_user"] = "pyworkflow"
|
|
592
|
+
config_data["mysql_password"] = "pyworkflow"
|
|
593
|
+
config_data["mysql_database"] = "pyworkflow"
|
|
594
|
+
else:
|
|
595
|
+
print_info("\nConfigure MySQL connection:")
|
|
596
|
+
config_data["mysql_host"] = input_text(
|
|
597
|
+
"MySQL host:",
|
|
598
|
+
default="localhost",
|
|
599
|
+
)
|
|
600
|
+
config_data["mysql_port"] = input_text(
|
|
601
|
+
"MySQL port:",
|
|
602
|
+
default="3306",
|
|
603
|
+
)
|
|
604
|
+
config_data["mysql_database"] = input_text(
|
|
605
|
+
"Database name:",
|
|
606
|
+
default="pyworkflow",
|
|
607
|
+
)
|
|
608
|
+
config_data["mysql_user"] = input_text(
|
|
609
|
+
"Database user:",
|
|
610
|
+
default="pyworkflow",
|
|
611
|
+
)
|
|
612
|
+
config_data["mysql_password"] = input_text(
|
|
613
|
+
"Database password:",
|
|
614
|
+
default="pyworkflow",
|
|
615
|
+
)
|
|
616
|
+
|
|
494
617
|
# DynamoDB configuration
|
|
495
618
|
elif storage_type == "dynamodb":
|
|
496
619
|
if non_interactive:
|
|
@@ -517,6 +640,40 @@ def _run_interactive_configuration(
|
|
|
517
640
|
)
|
|
518
641
|
config_data["dynamodb_endpoint_url"] = endpoint
|
|
519
642
|
|
|
643
|
+
# Cassandra configuration
|
|
644
|
+
elif storage_type == "cassandra":
|
|
645
|
+
if non_interactive:
|
|
646
|
+
config_data["cassandra_contact_points"] = "localhost"
|
|
647
|
+
config_data["cassandra_port"] = "9042"
|
|
648
|
+
config_data["cassandra_keyspace"] = "pyworkflow"
|
|
649
|
+
else:
|
|
650
|
+
print_info("\nConfigure Cassandra connection:")
|
|
651
|
+
contact_points = input_text(
|
|
652
|
+
"Cassandra contact points (comma-separated):",
|
|
653
|
+
default="localhost",
|
|
654
|
+
)
|
|
655
|
+
config_data["cassandra_contact_points"] = contact_points
|
|
656
|
+
|
|
657
|
+
config_data["cassandra_port"] = input_text(
|
|
658
|
+
"Cassandra port:",
|
|
659
|
+
default="9042",
|
|
660
|
+
)
|
|
661
|
+
config_data["cassandra_keyspace"] = input_text(
|
|
662
|
+
"Keyspace name:",
|
|
663
|
+
default="pyworkflow",
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Optional authentication
|
|
667
|
+
if confirm("Use Cassandra authentication?", default=False):
|
|
668
|
+
config_data["cassandra_user"] = input_text(
|
|
669
|
+
"Cassandra user:",
|
|
670
|
+
default="cassandra",
|
|
671
|
+
)
|
|
672
|
+
config_data["cassandra_password"] = input_text(
|
|
673
|
+
"Cassandra password:",
|
|
674
|
+
default="cassandra",
|
|
675
|
+
)
|
|
676
|
+
|
|
520
677
|
return config_data
|
|
521
678
|
|
|
522
679
|
|
|
@@ -543,6 +700,22 @@ def _setup_docker_infrastructure(
|
|
|
543
700
|
postgres_password=config_data.get("postgres_password", "pyworkflow"),
|
|
544
701
|
postgres_database=config_data.get("postgres_database", "pyworkflow"),
|
|
545
702
|
)
|
|
703
|
+
elif storage_type == "cassandra":
|
|
704
|
+
compose_content = generate_cassandra_docker_compose_content(
|
|
705
|
+
cassandra_host="cassandra",
|
|
706
|
+
cassandra_port=int(config_data.get("cassandra_port", "9042")),
|
|
707
|
+
cassandra_keyspace=config_data.get("cassandra_keyspace", "pyworkflow"),
|
|
708
|
+
cassandra_user=config_data.get("cassandra_user"),
|
|
709
|
+
cassandra_password=config_data.get("cassandra_password"),
|
|
710
|
+
)
|
|
711
|
+
elif storage_type == "mysql":
|
|
712
|
+
compose_content = generate_mysql_docker_compose_content(
|
|
713
|
+
mysql_host="mysql",
|
|
714
|
+
mysql_port=int(config_data.get("mysql_port", "3306")),
|
|
715
|
+
mysql_user=config_data.get("mysql_user", "pyworkflow"),
|
|
716
|
+
mysql_password=config_data.get("mysql_password", "pyworkflow"),
|
|
717
|
+
mysql_database=config_data.get("mysql_database", "pyworkflow"),
|
|
718
|
+
)
|
|
546
719
|
else:
|
|
547
720
|
compose_content = generate_docker_compose_content(
|
|
548
721
|
storage_type=storage_type,
|
|
@@ -574,10 +747,14 @@ def _setup_docker_infrastructure(
|
|
|
574
747
|
print_info("\n Starting services...")
|
|
575
748
|
print_info("")
|
|
576
749
|
|
|
577
|
-
# Include postgres in services to start if using
|
|
750
|
+
# Include postgres/cassandra/mysql in services to start if using those storage types
|
|
578
751
|
services_to_start = ["redis"]
|
|
579
752
|
if storage_type == "postgres":
|
|
580
753
|
services_to_start.insert(0, "postgres")
|
|
754
|
+
elif storage_type == "cassandra":
|
|
755
|
+
services_to_start.insert(0, "cassandra")
|
|
756
|
+
elif storage_type == "mysql":
|
|
757
|
+
services_to_start.insert(0, "mysql")
|
|
581
758
|
if dashboard_available:
|
|
582
759
|
services_to_start.extend(["dashboard-backend", "dashboard-frontend"])
|
|
583
760
|
|
|
@@ -594,6 +771,12 @@ def _setup_docker_infrastructure(
|
|
|
594
771
|
if storage_type == "postgres":
|
|
595
772
|
postgres_port = config_data.get("postgres_port", "5432")
|
|
596
773
|
ports_in_use = f"{postgres_port}, {ports_in_use}"
|
|
774
|
+
elif storage_type == "cassandra":
|
|
775
|
+
cassandra_port = config_data.get("cassandra_port", "9042")
|
|
776
|
+
ports_in_use = f"{cassandra_port}, {ports_in_use}"
|
|
777
|
+
elif storage_type == "mysql":
|
|
778
|
+
mysql_port = config_data.get("mysql_port", "3306")
|
|
779
|
+
ports_in_use = f"{mysql_port}, {ports_in_use}"
|
|
597
780
|
print_info(f" • Check if ports {ports_in_use} are already in use")
|
|
598
781
|
print_info(" • View logs: docker compose logs")
|
|
599
782
|
print_info(" • Try: docker compose down && docker compose up -d")
|
|
@@ -612,6 +795,16 @@ def _setup_docker_infrastructure(
|
|
|
612
795
|
pg_port = int(config_data.get("postgres_port", "5432"))
|
|
613
796
|
health_checks["PostgreSQL"] = {"type": "tcp", "host": "localhost", "port": pg_port}
|
|
614
797
|
|
|
798
|
+
# Add Cassandra health check if using cassandra storage
|
|
799
|
+
if storage_type == "cassandra":
|
|
800
|
+
cass_port = int(config_data.get("cassandra_port", "9042"))
|
|
801
|
+
health_checks["Cassandra"] = {"type": "tcp", "host": "localhost", "port": cass_port}
|
|
802
|
+
|
|
803
|
+
# Add MySQL health check if using mysql storage
|
|
804
|
+
if storage_type == "mysql":
|
|
805
|
+
mysql_port_num = int(config_data.get("mysql_port", "3306"))
|
|
806
|
+
health_checks["MySQL"] = {"type": "tcp", "host": "localhost", "port": mysql_port_num}
|
|
807
|
+
|
|
615
808
|
# Only check dashboard health if it was started
|
|
616
809
|
if dashboard_available:
|
|
617
810
|
health_checks["Dashboard Backend"] = {
|
|
@@ -672,6 +865,12 @@ def _show_next_steps(
|
|
|
672
865
|
if config_data.get("storage_type") == "postgres":
|
|
673
866
|
postgres_port = config_data.get("postgres_port", "5432")
|
|
674
867
|
print_info(f" • PostgreSQL: localhost:{postgres_port}")
|
|
868
|
+
elif config_data.get("storage_type") == "cassandra":
|
|
869
|
+
cassandra_port = config_data.get("cassandra_port", "9042")
|
|
870
|
+
print_info(f" • Cassandra: localhost:{cassandra_port}")
|
|
871
|
+
elif config_data.get("storage_type") == "mysql":
|
|
872
|
+
mysql_port = config_data.get("mysql_port", "3306")
|
|
873
|
+
print_info(f" • MySQL: localhost:{mysql_port}")
|
|
675
874
|
print_info(" • Redis: redis://localhost:6379")
|
|
676
875
|
if dashboard_available:
|
|
677
876
|
print_info(" • Dashboard: http://localhost:5173")
|
|
@@ -24,9 +24,19 @@ def generate_yaml_config(
|
|
|
24
24
|
postgres_user: str | None = None,
|
|
25
25
|
postgres_password: str | None = None,
|
|
26
26
|
postgres_database: str | None = None,
|
|
27
|
+
mysql_host: str | None = None,
|
|
28
|
+
mysql_port: str | None = None,
|
|
29
|
+
mysql_user: str | None = None,
|
|
30
|
+
mysql_password: str | None = None,
|
|
31
|
+
mysql_database: str | None = None,
|
|
27
32
|
dynamodb_table_name: str | None = None,
|
|
28
33
|
dynamodb_region: str | None = None,
|
|
29
34
|
dynamodb_endpoint_url: str | None = None,
|
|
35
|
+
cassandra_contact_points: list[str] | None = None,
|
|
36
|
+
cassandra_port: str | None = None,
|
|
37
|
+
cassandra_keyspace: str | None = None,
|
|
38
|
+
cassandra_user: str | None = None,
|
|
39
|
+
cassandra_password: str | None = None,
|
|
30
40
|
) -> str:
|
|
31
41
|
"""
|
|
32
42
|
Generate YAML configuration content.
|
|
@@ -34,7 +44,8 @@ def generate_yaml_config(
|
|
|
34
44
|
Args:
|
|
35
45
|
module: Optional workflow module path (e.g., "myapp.workflows")
|
|
36
46
|
runtime: Runtime type (e.g., "celery", "local")
|
|
37
|
-
storage_type: Storage backend type (e.g., "sqlite", "file", "memory", "postgres"
|
|
47
|
+
storage_type: Storage backend type (e.g., "sqlite", "file", "memory", "postgres",
|
|
48
|
+
"cassandra")
|
|
38
49
|
storage_path: Optional storage path for file/sqlite backends
|
|
39
50
|
broker_url: Celery broker URL
|
|
40
51
|
result_backend: Celery result backend URL
|
|
@@ -46,6 +57,11 @@ def generate_yaml_config(
|
|
|
46
57
|
dynamodb_table_name: Optional DynamoDB table name
|
|
47
58
|
dynamodb_region: Optional AWS region for DynamoDB
|
|
48
59
|
dynamodb_endpoint_url: Optional local DynamoDB endpoint URL
|
|
60
|
+
cassandra_contact_points: List of Cassandra contact points (for cassandra backend)
|
|
61
|
+
cassandra_port: Cassandra CQL native transport port (for cassandra backend)
|
|
62
|
+
cassandra_keyspace: Cassandra keyspace name (for cassandra backend)
|
|
63
|
+
cassandra_user: Optional Cassandra user (for cassandra backend)
|
|
64
|
+
cassandra_password: Optional Cassandra password (for cassandra backend)
|
|
49
65
|
|
|
50
66
|
Returns:
|
|
51
67
|
YAML configuration as string
|
|
@@ -84,6 +100,17 @@ def generate_yaml_config(
|
|
|
84
100
|
storage_config["password"] = postgres_password
|
|
85
101
|
if postgres_database:
|
|
86
102
|
storage_config["database"] = postgres_database
|
|
103
|
+
elif storage_type == "mysql":
|
|
104
|
+
if mysql_host:
|
|
105
|
+
storage_config["host"] = mysql_host
|
|
106
|
+
if mysql_port:
|
|
107
|
+
storage_config["port"] = int(mysql_port)
|
|
108
|
+
if mysql_user:
|
|
109
|
+
storage_config["user"] = mysql_user
|
|
110
|
+
if mysql_password:
|
|
111
|
+
storage_config["password"] = mysql_password
|
|
112
|
+
if mysql_database:
|
|
113
|
+
storage_config["database"] = mysql_database
|
|
87
114
|
elif storage_type == "dynamodb":
|
|
88
115
|
if dynamodb_table_name:
|
|
89
116
|
storage_config["table_name"] = dynamodb_table_name
|
|
@@ -91,6 +118,17 @@ def generate_yaml_config(
|
|
|
91
118
|
storage_config["region"] = dynamodb_region
|
|
92
119
|
if dynamodb_endpoint_url:
|
|
93
120
|
storage_config["endpoint_url"] = dynamodb_endpoint_url
|
|
121
|
+
elif storage_type == "cassandra":
|
|
122
|
+
if cassandra_contact_points:
|
|
123
|
+
storage_config["contact_points"] = cassandra_contact_points
|
|
124
|
+
if cassandra_port:
|
|
125
|
+
storage_config["port"] = int(cassandra_port)
|
|
126
|
+
if cassandra_keyspace:
|
|
127
|
+
storage_config["keyspace"] = cassandra_keyspace
|
|
128
|
+
if cassandra_user:
|
|
129
|
+
storage_config["username"] = cassandra_user
|
|
130
|
+
if cassandra_password:
|
|
131
|
+
storage_config["password"] = cassandra_password
|
|
94
132
|
config["storage"] = storage_config
|
|
95
133
|
|
|
96
134
|
# Celery configuration (only for celery runtime)
|
|
@@ -180,6 +218,13 @@ def load_yaml_config(path: Path | None = None) -> dict[str, Any]:
|
|
|
180
218
|
with open(path) as f:
|
|
181
219
|
config = yaml.safe_load(f)
|
|
182
220
|
|
|
221
|
+
# Handle empty config file (yaml.safe_load returns None for empty files)
|
|
222
|
+
if config is None:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
"Config file is empty or contains only comments. "
|
|
225
|
+
"Please add valid configuration or delete the file to create a new one."
|
|
226
|
+
)
|
|
227
|
+
|
|
183
228
|
# Ensure it's a dict
|
|
184
229
|
if not isinstance(config, dict):
|
|
185
230
|
raise ValueError("Config file must contain a YAML dictionary")
|
|
@@ -258,6 +303,14 @@ def display_config_summary(config: dict[str, Any]) -> list[str]:
|
|
|
258
303
|
user = storage.get("user", "pyworkflow")
|
|
259
304
|
lines.append(f" PostgreSQL: {user}@{host}:{port}/{database}")
|
|
260
305
|
|
|
306
|
+
# MySQL-specific config
|
|
307
|
+
if storage_type == "mysql":
|
|
308
|
+
host = storage.get("host", "localhost")
|
|
309
|
+
port = storage.get("port", 3306)
|
|
310
|
+
database = storage.get("database", "pyworkflow")
|
|
311
|
+
user = storage.get("user", "pyworkflow")
|
|
312
|
+
lines.append(f" MySQL: {user}@{host}:{port}/{database}")
|
|
313
|
+
|
|
261
314
|
# DynamoDB-specific config
|
|
262
315
|
if storage_type == "dynamodb":
|
|
263
316
|
if "table_name" in storage:
|
|
@@ -267,6 +320,16 @@ def display_config_summary(config: dict[str, Any]) -> list[str]:
|
|
|
267
320
|
if "endpoint_url" in storage:
|
|
268
321
|
lines.append(f" Endpoint URL: {storage['endpoint_url']}")
|
|
269
322
|
|
|
323
|
+
# Cassandra-specific config
|
|
324
|
+
if storage_type == "cassandra":
|
|
325
|
+
contact_points = storage.get("contact_points", ["localhost"])
|
|
326
|
+
port = storage.get("port", 9042)
|
|
327
|
+
keyspace = storage.get("keyspace", "pyworkflow")
|
|
328
|
+
hosts = ", ".join(contact_points) if isinstance(contact_points, list) else contact_points
|
|
329
|
+
lines.append(f" Cassandra: {hosts}:{port}/{keyspace}")
|
|
330
|
+
if "username" in storage:
|
|
331
|
+
lines.append(f" Cassandra User: {storage['username']}")
|
|
332
|
+
|
|
270
333
|
# Celery (if applicable)
|
|
271
334
|
if runtime == "celery" and "celery" in config:
|
|
272
335
|
celery = config["celery"]
|
|
@@ -314,10 +377,20 @@ def validate_config(config: dict[str, Any]) -> tuple[bool, list[str]]:
|
|
|
314
377
|
storage_type = storage.get("type")
|
|
315
378
|
if not storage_type:
|
|
316
379
|
errors.append("Missing storage 'type'")
|
|
317
|
-
elif storage_type not in [
|
|
380
|
+
elif storage_type not in [
|
|
381
|
+
"file",
|
|
382
|
+
"memory",
|
|
383
|
+
"sqlite",
|
|
384
|
+
"redis",
|
|
385
|
+
"postgres",
|
|
386
|
+
"mysql",
|
|
387
|
+
"dynamodb",
|
|
388
|
+
"cassandra",
|
|
389
|
+
]:
|
|
318
390
|
errors.append(
|
|
319
391
|
f"Invalid storage type: {storage_type}. "
|
|
320
|
-
"Must be 'file', 'memory', 'sqlite', 'redis', 'postgres'
|
|
392
|
+
"Must be 'file', 'memory', 'sqlite', 'redis', 'postgres', 'mysql', 'dynamodb' "
|
|
393
|
+
"or 'cassandra'"
|
|
321
394
|
)
|
|
322
395
|
|
|
323
396
|
# Check Celery config if using celery runtime
|