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.
Files changed (196) hide show
  1. dashboard/backend/app/__init__.py +1 -0
  2. dashboard/backend/app/config.py +32 -0
  3. dashboard/backend/app/controllers/__init__.py +6 -0
  4. dashboard/backend/app/controllers/run_controller.py +86 -0
  5. dashboard/backend/app/controllers/workflow_controller.py +33 -0
  6. dashboard/backend/app/dependencies/__init__.py +5 -0
  7. dashboard/backend/app/dependencies/storage.py +50 -0
  8. dashboard/backend/app/repositories/__init__.py +6 -0
  9. dashboard/backend/app/repositories/run_repository.py +80 -0
  10. dashboard/backend/app/repositories/workflow_repository.py +27 -0
  11. dashboard/backend/app/rest/__init__.py +8 -0
  12. dashboard/backend/app/rest/v1/__init__.py +12 -0
  13. dashboard/backend/app/rest/v1/health.py +33 -0
  14. dashboard/backend/app/rest/v1/runs.py +133 -0
  15. dashboard/backend/app/rest/v1/workflows.py +41 -0
  16. dashboard/backend/app/schemas/__init__.py +23 -0
  17. dashboard/backend/app/schemas/common.py +16 -0
  18. dashboard/backend/app/schemas/event.py +24 -0
  19. dashboard/backend/app/schemas/hook.py +25 -0
  20. dashboard/backend/app/schemas/run.py +54 -0
  21. dashboard/backend/app/schemas/step.py +28 -0
  22. dashboard/backend/app/schemas/workflow.py +31 -0
  23. dashboard/backend/app/server.py +87 -0
  24. dashboard/backend/app/services/__init__.py +6 -0
  25. dashboard/backend/app/services/run_service.py +240 -0
  26. dashboard/backend/app/services/workflow_service.py +155 -0
  27. dashboard/backend/main.py +18 -0
  28. docs/concepts/cancellation.mdx +362 -0
  29. docs/concepts/continue-as-new.mdx +434 -0
  30. docs/concepts/events.mdx +266 -0
  31. docs/concepts/fault-tolerance.mdx +370 -0
  32. docs/concepts/hooks.mdx +552 -0
  33. docs/concepts/limitations.mdx +167 -0
  34. docs/concepts/schedules.mdx +775 -0
  35. docs/concepts/sleep.mdx +312 -0
  36. docs/concepts/steps.mdx +301 -0
  37. docs/concepts/workflows.mdx +255 -0
  38. docs/guides/cli.mdx +942 -0
  39. docs/guides/configuration.mdx +560 -0
  40. docs/introduction.mdx +155 -0
  41. docs/quickstart.mdx +279 -0
  42. examples/__init__.py +1 -0
  43. examples/celery/__init__.py +1 -0
  44. examples/celery/durable/docker-compose.yml +55 -0
  45. examples/celery/durable/pyworkflow.config.yaml +12 -0
  46. examples/celery/durable/workflows/__init__.py +122 -0
  47. examples/celery/durable/workflows/basic.py +87 -0
  48. examples/celery/durable/workflows/batch_processing.py +102 -0
  49. examples/celery/durable/workflows/cancellation.py +273 -0
  50. examples/celery/durable/workflows/child_workflow_patterns.py +240 -0
  51. examples/celery/durable/workflows/child_workflows.py +202 -0
  52. examples/celery/durable/workflows/continue_as_new.py +260 -0
  53. examples/celery/durable/workflows/fault_tolerance.py +210 -0
  54. examples/celery/durable/workflows/hooks.py +211 -0
  55. examples/celery/durable/workflows/idempotency.py +112 -0
  56. examples/celery/durable/workflows/long_running.py +99 -0
  57. examples/celery/durable/workflows/retries.py +101 -0
  58. examples/celery/durable/workflows/schedules.py +209 -0
  59. examples/celery/transient/01_basic_workflow.py +91 -0
  60. examples/celery/transient/02_fault_tolerance.py +257 -0
  61. examples/celery/transient/__init__.py +20 -0
  62. examples/celery/transient/pyworkflow.config.yaml +25 -0
  63. examples/local/__init__.py +1 -0
  64. examples/local/durable/01_basic_workflow.py +94 -0
  65. examples/local/durable/02_file_storage.py +132 -0
  66. examples/local/durable/03_retries.py +169 -0
  67. examples/local/durable/04_long_running.py +119 -0
  68. examples/local/durable/05_event_log.py +145 -0
  69. examples/local/durable/06_idempotency.py +148 -0
  70. examples/local/durable/07_hooks.py +334 -0
  71. examples/local/durable/08_cancellation.py +233 -0
  72. examples/local/durable/09_child_workflows.py +198 -0
  73. examples/local/durable/10_child_workflow_patterns.py +265 -0
  74. examples/local/durable/11_continue_as_new.py +249 -0
  75. examples/local/durable/12_schedules.py +198 -0
  76. examples/local/durable/__init__.py +1 -0
  77. examples/local/transient/01_quick_tasks.py +87 -0
  78. examples/local/transient/02_retries.py +130 -0
  79. examples/local/transient/03_sleep.py +141 -0
  80. examples/local/transient/__init__.py +1 -0
  81. pyworkflow/__init__.py +256 -0
  82. pyworkflow/aws/__init__.py +68 -0
  83. pyworkflow/aws/context.py +234 -0
  84. pyworkflow/aws/handler.py +184 -0
  85. pyworkflow/aws/testing.py +310 -0
  86. pyworkflow/celery/__init__.py +41 -0
  87. pyworkflow/celery/app.py +198 -0
  88. pyworkflow/celery/scheduler.py +315 -0
  89. pyworkflow/celery/tasks.py +1746 -0
  90. pyworkflow/cli/__init__.py +132 -0
  91. pyworkflow/cli/__main__.py +6 -0
  92. pyworkflow/cli/commands/__init__.py +1 -0
  93. pyworkflow/cli/commands/hooks.py +640 -0
  94. pyworkflow/cli/commands/quickstart.py +495 -0
  95. pyworkflow/cli/commands/runs.py +773 -0
  96. pyworkflow/cli/commands/scheduler.py +130 -0
  97. pyworkflow/cli/commands/schedules.py +794 -0
  98. pyworkflow/cli/commands/setup.py +703 -0
  99. pyworkflow/cli/commands/worker.py +413 -0
  100. pyworkflow/cli/commands/workflows.py +1257 -0
  101. pyworkflow/cli/output/__init__.py +1 -0
  102. pyworkflow/cli/output/formatters.py +321 -0
  103. pyworkflow/cli/output/styles.py +121 -0
  104. pyworkflow/cli/utils/__init__.py +1 -0
  105. pyworkflow/cli/utils/async_helpers.py +30 -0
  106. pyworkflow/cli/utils/config.py +130 -0
  107. pyworkflow/cli/utils/config_generator.py +344 -0
  108. pyworkflow/cli/utils/discovery.py +53 -0
  109. pyworkflow/cli/utils/docker_manager.py +651 -0
  110. pyworkflow/cli/utils/interactive.py +364 -0
  111. pyworkflow/cli/utils/storage.py +115 -0
  112. pyworkflow/config.py +329 -0
  113. pyworkflow/context/__init__.py +63 -0
  114. pyworkflow/context/aws.py +230 -0
  115. pyworkflow/context/base.py +416 -0
  116. pyworkflow/context/local.py +930 -0
  117. pyworkflow/context/mock.py +381 -0
  118. pyworkflow/core/__init__.py +0 -0
  119. pyworkflow/core/exceptions.py +353 -0
  120. pyworkflow/core/registry.py +313 -0
  121. pyworkflow/core/scheduled.py +328 -0
  122. pyworkflow/core/step.py +494 -0
  123. pyworkflow/core/workflow.py +294 -0
  124. pyworkflow/discovery.py +248 -0
  125. pyworkflow/engine/__init__.py +0 -0
  126. pyworkflow/engine/events.py +879 -0
  127. pyworkflow/engine/executor.py +682 -0
  128. pyworkflow/engine/replay.py +273 -0
  129. pyworkflow/observability/__init__.py +19 -0
  130. pyworkflow/observability/logging.py +234 -0
  131. pyworkflow/primitives/__init__.py +33 -0
  132. pyworkflow/primitives/child_handle.py +174 -0
  133. pyworkflow/primitives/child_workflow.py +372 -0
  134. pyworkflow/primitives/continue_as_new.py +101 -0
  135. pyworkflow/primitives/define_hook.py +150 -0
  136. pyworkflow/primitives/hooks.py +97 -0
  137. pyworkflow/primitives/resume_hook.py +210 -0
  138. pyworkflow/primitives/schedule.py +545 -0
  139. pyworkflow/primitives/shield.py +96 -0
  140. pyworkflow/primitives/sleep.py +100 -0
  141. pyworkflow/runtime/__init__.py +21 -0
  142. pyworkflow/runtime/base.py +179 -0
  143. pyworkflow/runtime/celery.py +310 -0
  144. pyworkflow/runtime/factory.py +101 -0
  145. pyworkflow/runtime/local.py +706 -0
  146. pyworkflow/scheduler/__init__.py +9 -0
  147. pyworkflow/scheduler/local.py +248 -0
  148. pyworkflow/serialization/__init__.py +0 -0
  149. pyworkflow/serialization/decoder.py +146 -0
  150. pyworkflow/serialization/encoder.py +162 -0
  151. pyworkflow/storage/__init__.py +54 -0
  152. pyworkflow/storage/base.py +612 -0
  153. pyworkflow/storage/config.py +185 -0
  154. pyworkflow/storage/dynamodb.py +1315 -0
  155. pyworkflow/storage/file.py +827 -0
  156. pyworkflow/storage/memory.py +549 -0
  157. pyworkflow/storage/postgres.py +1161 -0
  158. pyworkflow/storage/schemas.py +486 -0
  159. pyworkflow/storage/sqlite.py +1136 -0
  160. pyworkflow/utils/__init__.py +0 -0
  161. pyworkflow/utils/duration.py +177 -0
  162. pyworkflow/utils/schedule.py +391 -0
  163. pyworkflow_engine-0.1.7.dist-info/METADATA +687 -0
  164. pyworkflow_engine-0.1.7.dist-info/RECORD +196 -0
  165. pyworkflow_engine-0.1.7.dist-info/WHEEL +5 -0
  166. pyworkflow_engine-0.1.7.dist-info/entry_points.txt +2 -0
  167. pyworkflow_engine-0.1.7.dist-info/licenses/LICENSE +21 -0
  168. pyworkflow_engine-0.1.7.dist-info/top_level.txt +5 -0
  169. tests/examples/__init__.py +0 -0
  170. tests/integration/__init__.py +0 -0
  171. tests/integration/test_cancellation.py +330 -0
  172. tests/integration/test_child_workflows.py +439 -0
  173. tests/integration/test_continue_as_new.py +428 -0
  174. tests/integration/test_dynamodb_storage.py +1146 -0
  175. tests/integration/test_fault_tolerance.py +369 -0
  176. tests/integration/test_schedule_storage.py +484 -0
  177. tests/unit/__init__.py +0 -0
  178. tests/unit/backends/__init__.py +1 -0
  179. tests/unit/backends/test_dynamodb_storage.py +1554 -0
  180. tests/unit/backends/test_postgres_storage.py +1281 -0
  181. tests/unit/backends/test_sqlite_storage.py +1460 -0
  182. tests/unit/conftest.py +41 -0
  183. tests/unit/test_cancellation.py +364 -0
  184. tests/unit/test_child_workflows.py +680 -0
  185. tests/unit/test_continue_as_new.py +441 -0
  186. tests/unit/test_event_limits.py +316 -0
  187. tests/unit/test_executor.py +320 -0
  188. tests/unit/test_fault_tolerance.py +334 -0
  189. tests/unit/test_hooks.py +495 -0
  190. tests/unit/test_registry.py +261 -0
  191. tests/unit/test_replay.py +420 -0
  192. tests/unit/test_schedule_schemas.py +285 -0
  193. tests/unit/test_schedule_utils.py +286 -0
  194. tests/unit/test_scheduled_workflow.py +274 -0
  195. tests/unit/test_step.py +353 -0
  196. tests/unit/test_workflow.py +243 -0
@@ -0,0 +1,495 @@
1
+ """Quickstart command to scaffold a new PyWorkflow project."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import click
7
+
8
+ from pyworkflow.cli.output.formatters import (
9
+ print_error,
10
+ print_info,
11
+ print_success,
12
+ print_warning,
13
+ )
14
+ from pyworkflow.cli.utils.config_generator import (
15
+ generate_yaml_config,
16
+ write_yaml_config,
17
+ )
18
+ from pyworkflow.cli.utils.docker_manager import (
19
+ check_docker_available,
20
+ check_service_health,
21
+ generate_docker_compose_content,
22
+ run_docker_command,
23
+ write_docker_compose,
24
+ )
25
+ from pyworkflow.cli.utils.interactive import (
26
+ confirm,
27
+ select,
28
+ )
29
+
30
+ # Template content for sample workflows
31
+ WORKFLOWS_INIT_TEMPLATE = '''"""Project workflows."""
32
+ from .orders import process_order
33
+ from .notifications import send_notification
34
+
35
+ __all__ = [
36
+ "process_order",
37
+ "send_notification",
38
+ ]
39
+ '''
40
+
41
+ ORDERS_WORKFLOW_TEMPLATE = '''"""Order processing workflow example."""
42
+ from pyworkflow import workflow, step
43
+
44
+
45
+ @step()
46
+ async def validate_order(order_id: str) -> dict:
47
+ """Validate the order exists and is valid."""
48
+ # In a real app, check database or external service
49
+ return {"order_id": order_id, "valid": True}
50
+
51
+
52
+ @step()
53
+ async def process_payment(order_id: str, amount: float) -> dict:
54
+ """Process payment for the order."""
55
+ # In a real app, integrate with payment provider
56
+ return {"order_id": order_id, "payment_status": "completed", "amount": amount}
57
+
58
+
59
+ @step()
60
+ async def update_inventory(order_id: str) -> dict:
61
+ """Update inventory after order."""
62
+ # In a real app, update inventory database
63
+ return {"order_id": order_id, "inventory_updated": True}
64
+
65
+
66
+ @workflow()
67
+ async def process_order(order_id: str, amount: float = 99.99) -> dict:
68
+ """
69
+ Process an order through validation, payment, and inventory update.
70
+
71
+ This is a sample workflow demonstrating:
72
+ - Multiple steps executed in sequence
73
+ - Data passing between steps
74
+ - Automatic retry on failures
75
+
76
+ Run with:
77
+ pyworkflow workflows run process_order --input '{"order_id": "123", "amount": 49.99}'
78
+ """
79
+ validation = await validate_order(order_id)
80
+ if not validation["valid"]:
81
+ raise ValueError(f"Order {order_id} is not valid")
82
+
83
+ payment = await process_payment(order_id, amount)
84
+ inventory = await update_inventory(order_id)
85
+
86
+ return {
87
+ "order_id": order_id,
88
+ "status": "completed",
89
+ "payment": payment,
90
+ "inventory": inventory,
91
+ }
92
+ '''
93
+
94
+ NOTIFICATIONS_WORKFLOW_TEMPLATE = '''"""Notification workflow example."""
95
+ from pyworkflow import workflow, step, sleep
96
+
97
+
98
+ @step()
99
+ async def send_email(to: str, subject: str, body: str) -> dict:
100
+ """Send an email notification."""
101
+ # In a real app, integrate with email service (SendGrid, SES, etc.)
102
+ return {"sent": True, "to": to, "subject": subject}
103
+
104
+
105
+ @step()
106
+ async def send_sms(phone: str, message: str) -> dict:
107
+ """Send an SMS notification."""
108
+ # In a real app, integrate with SMS service (Twilio, etc.)
109
+ return {"sent": True, "phone": phone}
110
+
111
+
112
+ @workflow()
113
+ async def send_notification(
114
+ user_id: str,
115
+ message: str,
116
+ channels: list[str] | None = None,
117
+ ) -> dict:
118
+ """
119
+ Send notifications through multiple channels.
120
+
121
+ This workflow demonstrates:
122
+ - Conditional step execution
123
+ - Sleep/delay functionality
124
+ - Multiple notification channels
125
+
126
+ Run with:
127
+ pyworkflow workflows run send_notification --input '{"user_id": "user-123", "message": "Hello!"}'
128
+ """
129
+ if channels is None:
130
+ channels = ["email"]
131
+
132
+ results = {"user_id": user_id, "notifications": []}
133
+
134
+ if "email" in channels:
135
+ email_result = await send_email(
136
+ to=f"{user_id}@example.com",
137
+ subject="Notification",
138
+ body=message,
139
+ )
140
+ results["notifications"].append({"channel": "email", **email_result})
141
+
142
+ if "sms" in channels:
143
+ # Add small delay between channels
144
+ await sleep("1s")
145
+ sms_result = await send_sms(
146
+ phone="+1234567890",
147
+ message=message,
148
+ )
149
+ results["notifications"].append({"channel": "sms", **sms_result})
150
+
151
+ return results
152
+ '''
153
+
154
+
155
+ def _check_sqlite_available() -> bool:
156
+ """Check if SQLite is available in the Python build."""
157
+ try:
158
+ import sqlite3 # noqa: F401
159
+
160
+ return True
161
+ except ImportError:
162
+ return False
163
+
164
+
165
+ @click.command(name="quickstart")
166
+ @click.option(
167
+ "--non-interactive",
168
+ is_flag=True,
169
+ help="Run without prompts (use defaults)",
170
+ )
171
+ @click.option(
172
+ "--skip-docker",
173
+ is_flag=True,
174
+ help="Skip Docker services setup",
175
+ )
176
+ @click.option(
177
+ "--template",
178
+ type=click.Choice(["basic"]),
179
+ default="basic",
180
+ help="Project template to use",
181
+ )
182
+ @click.option(
183
+ "--storage",
184
+ type=click.Choice(["sqlite", "file"], case_sensitive=False),
185
+ help="Storage backend type",
186
+ )
187
+ @click.pass_context
188
+ def quickstart(
189
+ ctx: click.Context,
190
+ non_interactive: bool,
191
+ skip_docker: bool,
192
+ template: str,
193
+ storage: str | None,
194
+ ) -> None:
195
+ """
196
+ Create a new PyWorkflow project with sample workflows.
197
+
198
+ This command will:
199
+ 1. Create a workflows/ package with sample workflows
200
+ 2. Generate pyworkflow.config.yaml
201
+ 3. Optionally start Docker services (Redis + Dashboard)
202
+
203
+ Examples:
204
+
205
+ # Interactive quickstart
206
+ $ pyworkflow quickstart
207
+
208
+ # Non-interactive with defaults
209
+ $ pyworkflow quickstart --non-interactive
210
+
211
+ # Without Docker
212
+ $ pyworkflow quickstart --skip-docker
213
+ """
214
+ try:
215
+ _run_quickstart(
216
+ ctx=ctx,
217
+ non_interactive=non_interactive,
218
+ skip_docker=skip_docker,
219
+ template=template,
220
+ storage_override=storage,
221
+ )
222
+ except click.Abort:
223
+ print_warning("\nQuickstart cancelled by user")
224
+ sys.exit(1)
225
+ except Exception as e:
226
+ print_error(f"\nQuickstart failed: {str(e)}")
227
+ if ctx.obj and ctx.obj.get("verbose"):
228
+ raise
229
+ sys.exit(1)
230
+
231
+
232
+ def _run_quickstart(
233
+ ctx: click.Context,
234
+ non_interactive: bool,
235
+ skip_docker: bool,
236
+ template: str,
237
+ storage_override: str | None,
238
+ ) -> None:
239
+ """Main quickstart workflow."""
240
+ # 1. Welcome banner
241
+ _print_welcome()
242
+
243
+ # 2. Check for existing files
244
+ cwd = Path.cwd()
245
+ workflows_dir = cwd / "workflows"
246
+ config_path = cwd / "pyworkflow.config.yaml"
247
+
248
+ if workflows_dir.exists():
249
+ print_warning(f"Directory already exists: {workflows_dir}")
250
+ if not non_interactive:
251
+ if not confirm("Overwrite existing workflows directory?", default=False):
252
+ raise click.Abort()
253
+ print_info("")
254
+
255
+ if config_path.exists():
256
+ print_warning(f"Config already exists: {config_path}")
257
+ if not non_interactive:
258
+ if not confirm("Overwrite existing config?", default=False):
259
+ raise click.Abort()
260
+ print_info("")
261
+
262
+ # 3. Template selection (currently only basic)
263
+ if not non_interactive:
264
+ print_info("Select a project template:\n")
265
+ template = select(
266
+ "Template:",
267
+ choices=[
268
+ {
269
+ "name": "Basic - Order processing and notifications (2 workflows)",
270
+ "value": "basic",
271
+ },
272
+ ],
273
+ )
274
+
275
+ # 4. Storage backend selection
276
+ sqlite_available = _check_sqlite_available()
277
+ if storage_override:
278
+ storage_type = storage_override.lower()
279
+ if storage_type == "sqlite" and not sqlite_available:
280
+ print_error("SQLite is not available in your Python build")
281
+ print_info("Use --storage file or install libsqlite3-dev and rebuild Python")
282
+ raise click.Abort()
283
+ elif non_interactive:
284
+ storage_type = "sqlite" if sqlite_available else "file"
285
+ else:
286
+ print_info("")
287
+ choices = []
288
+ if sqlite_available:
289
+ choices.append(
290
+ {"name": "SQLite - Single file database (recommended)", "value": "sqlite"}
291
+ )
292
+ choices.append(
293
+ {
294
+ "name": "File - JSON files on disk"
295
+ + (" (recommended)" if not sqlite_available else ""),
296
+ "value": "file",
297
+ }
298
+ )
299
+
300
+ storage_type = select("Storage backend:", choices=choices)
301
+
302
+ # 5. Docker prompt
303
+ docker_available, docker_error = check_docker_available()
304
+ start_docker = False
305
+
306
+ if not skip_docker:
307
+ if not docker_available:
308
+ print_warning(f"\nDocker: {docker_error}")
309
+ print_info("Skipping Docker setup. You can run 'pyworkflow setup' later.\n")
310
+ elif non_interactive:
311
+ start_docker = True
312
+ else:
313
+ print_info("")
314
+ start_docker = confirm("Start Docker services (Redis + Dashboard)?", default=True)
315
+
316
+ # 6. Create project structure
317
+ print_info("\nCreating project structure...")
318
+ _create_project_files(cwd, template)
319
+
320
+ # 7. Generate config
321
+ storage_path = (
322
+ "pyworkflow_data/pyworkflow.db" if storage_type == "sqlite" else "pyworkflow_data"
323
+ )
324
+
325
+ yaml_content = generate_yaml_config(
326
+ module="workflows",
327
+ runtime="celery",
328
+ storage_type=storage_type,
329
+ storage_path=storage_path,
330
+ broker_url="redis://localhost:6379/0",
331
+ result_backend="redis://localhost:6379/1",
332
+ )
333
+
334
+ write_yaml_config(yaml_content, config_path, backup=True)
335
+ print_success(f" Created: {config_path.name}")
336
+
337
+ # 8. Docker setup
338
+ dashboard_available = False
339
+ if start_docker:
340
+ dashboard_available = _setup_docker(cwd, storage_type, storage_path)
341
+
342
+ # 9. Show next steps
343
+ _show_next_steps(start_docker, dashboard_available)
344
+
345
+
346
+ def _print_welcome() -> None:
347
+ """Print welcome banner."""
348
+ print_info("")
349
+ print_info("=" * 60)
350
+ print_info(" PyWorkflow Quickstart")
351
+ print_info("=" * 60)
352
+ print_info("")
353
+
354
+
355
+ def _create_project_files(cwd: Path, template: str) -> None:
356
+ """Create project structure based on template."""
357
+ workflows_dir = cwd / "workflows"
358
+ workflows_dir.mkdir(exist_ok=True)
359
+
360
+ # Write template files
361
+ files = {
362
+ workflows_dir / "__init__.py": WORKFLOWS_INIT_TEMPLATE,
363
+ workflows_dir / "orders.py": ORDERS_WORKFLOW_TEMPLATE,
364
+ workflows_dir / "notifications.py": NOTIFICATIONS_WORKFLOW_TEMPLATE,
365
+ }
366
+
367
+ for file_path, content in files.items():
368
+ file_path.write_text(content)
369
+ print_success(f" Created: {file_path.relative_to(cwd)}")
370
+
371
+
372
+ def _setup_docker(
373
+ cwd: Path,
374
+ storage_type: str,
375
+ storage_path: str,
376
+ ) -> bool:
377
+ """Set up Docker infrastructure.
378
+
379
+ Returns:
380
+ True if dashboard is available, False otherwise
381
+ """
382
+ print_info("\nSetting up Docker services...")
383
+
384
+ # Generate docker-compose.yml
385
+ compose_content = generate_docker_compose_content(
386
+ storage_type=storage_type,
387
+ storage_path=storage_path,
388
+ )
389
+
390
+ compose_path = cwd / "docker-compose.yml"
391
+ write_docker_compose(compose_content, compose_path)
392
+ print_success(f" Created: {compose_path.name}")
393
+
394
+ # Pull images
395
+ print_info("\n Pulling Docker images...")
396
+ print_info("")
397
+ pull_success, _ = run_docker_command(
398
+ ["pull"],
399
+ compose_file=compose_path,
400
+ stream_output=True,
401
+ )
402
+
403
+ dashboard_available = pull_success
404
+ if not pull_success:
405
+ print_warning("\n Failed to pull dashboard images")
406
+ print_info(" Continuing with Redis setup only...")
407
+
408
+ # Start services
409
+ print_info("\n Starting services...")
410
+ print_info("")
411
+
412
+ services_to_start = ["redis"]
413
+ if dashboard_available:
414
+ services_to_start.extend(["dashboard-backend", "dashboard-frontend"])
415
+
416
+ success, _ = run_docker_command(
417
+ ["up", "-d"] + services_to_start,
418
+ compose_file=compose_path,
419
+ stream_output=True,
420
+ )
421
+
422
+ if not success:
423
+ print_error("\n Failed to start services")
424
+ print_info(" Try: docker compose down && docker compose up -d")
425
+ return False
426
+
427
+ print_success("\n Services started")
428
+
429
+ # Health checks
430
+ print_info("\n Checking service health...")
431
+ health_checks = {
432
+ "Redis": {"type": "tcp", "host": "localhost", "port": 6379},
433
+ }
434
+
435
+ if dashboard_available:
436
+ health_checks["Dashboard Backend"] = {
437
+ "type": "http",
438
+ "url": "http://localhost:8585/api/v1/health",
439
+ }
440
+ health_checks["Dashboard Frontend"] = {
441
+ "type": "http",
442
+ "url": "http://localhost:5173",
443
+ }
444
+
445
+ health_results = check_service_health(health_checks)
446
+
447
+ for service_name, healthy in health_results.items():
448
+ if healthy:
449
+ print_success(f" {service_name}: Ready")
450
+ else:
451
+ print_warning(f" {service_name}: Not responding (may still be starting)")
452
+
453
+ return dashboard_available
454
+
455
+
456
+ def _show_next_steps(docker_started: bool, dashboard_available: bool) -> None:
457
+ """Display next steps."""
458
+ print_info("\n" + "=" * 60)
459
+ print_success("Project Created!")
460
+ print_info("=" * 60)
461
+
462
+ print_info("\nProject structure:")
463
+ print_info(" workflows/")
464
+ print_info(" __init__.py")
465
+ print_info(" orders.py (process_order workflow)")
466
+ print_info(" notifications.py (send_notification workflow)")
467
+ print_info(" pyworkflow.config.yaml")
468
+
469
+ if docker_started:
470
+ print_info("\nServices running:")
471
+ print_info(" Redis: redis://localhost:6379")
472
+ if dashboard_available:
473
+ print_info(" Dashboard: http://localhost:5173")
474
+ print_info(" Dashboard API: http://localhost:8585/docs")
475
+
476
+ print_info("\nNext steps:")
477
+ print_info("")
478
+ print_info(" 1. Start a worker:")
479
+ print_info(" $ pyworkflow worker start")
480
+ print_info("")
481
+ print_info(" 2. Run a workflow:")
482
+ print_info(" $ pyworkflow workflows run process_order \\")
483
+ print_info(' --input \'{"order_id": "123", "amount": 49.99}\'')
484
+
485
+ if docker_started and dashboard_available:
486
+ print_info("")
487
+ print_info(" 3. View the dashboard:")
488
+ print_info(" Open http://localhost:5173 in your browser")
489
+
490
+ if docker_started:
491
+ print_info("")
492
+ print_info("To stop services:")
493
+ print_info(" $ docker compose down")
494
+
495
+ print_info("")