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,687 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyworkflow-engine
3
+ Version: 0.1.7
4
+ Summary: A Python implementation of durable, event-sourced workflows inspired by Vercel Workflow
5
+ Author: PyWorkflow Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://docs.pyworkflow.dev
8
+ Project-URL: Documentation, https://docs.pyworkflow.dev
9
+ Project-URL: Repository, https://github.com/QualityUnit/pyworkflow
10
+ Project-URL: Issues, https://github.com/QualityUnit/pyworkflow/issues
11
+ Keywords: workflow,durable,event-sourcing,celery,async,orchestration
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: System :: Distributed Computing
21
+ Classifier: Framework :: Celery
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: celery<6.0.0,>=5.3.0
26
+ Requires-Dist: cloudpickle>=3.0.0
27
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
28
+ Requires-Dist: loguru>=0.7.0
29
+ Requires-Dist: click>=8.0.0
30
+ Requires-Dist: inquirerpy>=0.3.4; python_version < "4.0"
31
+ Requires-Dist: httpx>=0.25.0
32
+ Requires-Dist: python-dateutil>=2.8.0
33
+ Requires-Dist: filelock>=3.12.0
34
+ Requires-Dist: pyyaml>=6.0.0
35
+ Requires-Dist: croniter>=2.0.0
36
+ Provides-Extra: redis
37
+ Requires-Dist: redis>=5.0.0; extra == "redis"
38
+ Requires-Dist: celery[redis]<6.0.0,>=5.3.0; extra == "redis"
39
+ Provides-Extra: sqlite
40
+ Requires-Dist: aiosqlite>=0.19.0; extra == "sqlite"
41
+ Provides-Extra: postgres
42
+ Requires-Dist: asyncpg>=0.29.0; extra == "postgres"
43
+ Provides-Extra: aws
44
+ Requires-Dist: aws-durable-execution-sdk-python>=0.1.0; extra == "aws"
45
+ Provides-Extra: dynamodb
46
+ Requires-Dist: aiobotocore>=2.5.0; extra == "dynamodb"
47
+ Provides-Extra: all
48
+ Requires-Dist: redis>=5.0.0; extra == "all"
49
+ Requires-Dist: celery[redis]<6.0.0,>=5.3.0; extra == "all"
50
+ Requires-Dist: aiosqlite>=0.19.0; extra == "all"
51
+ Requires-Dist: asyncpg>=0.29.0; extra == "all"
52
+ Requires-Dist: aws-durable-execution-sdk-python>=0.1.0; extra == "all"
53
+ Provides-Extra: dev
54
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
55
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
56
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
57
+ Requires-Dist: pytest-celery>=0.0.0; extra == "dev"
58
+ Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
59
+ Requires-Dist: moto[dynamodb]>=5.0.0; extra == "dev"
60
+ Requires-Dist: black>=23.0.0; extra == "dev"
61
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
62
+ Requires-Dist: mypy>=1.7.0; extra == "dev"
63
+ Requires-Dist: pre-commit>=3.5.0; extra == "dev"
64
+ Requires-Dist: types-redis>=4.6.0; extra == "dev"
65
+ Requires-Dist: types-python-dateutil>=2.8.0; extra == "dev"
66
+ Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
67
+ Requires-Dist: flower>=2.0.0; extra == "dev"
68
+ Requires-Dist: redis>=5.0.0; extra == "dev"
69
+ Requires-Dist: celery[redis]<6.0.0,>=5.3.0; extra == "dev"
70
+ Requires-Dist: aiosqlite>=0.19.0; extra == "dev"
71
+ Requires-Dist: asyncpg>=0.29.0; extra == "dev"
72
+ Dynamic: license-file
73
+
74
+ # PyWorkflow
75
+
76
+ **Distributed, durable workflow orchestration for Python**
77
+
78
+ Build long-running, fault-tolerant workflows with automatic retry, sleep/delay capabilities, and complete observability. PyWorkflow uses event sourcing and Celery for production-grade distributed execution.
79
+
80
+ ---
81
+
82
+ ## What is PyWorkflow?
83
+
84
+ PyWorkflow is a workflow orchestration framework that enables you to build complex, long-running business processes as simple Python code. It handles the hard parts of distributed systems: fault tolerance, automatic retries, state management, and horizontal scaling.
85
+
86
+ ### Key Features
87
+
88
+ - **Distributed by Default**: All workflows execute across Celery workers for horizontal scaling
89
+ - **Durable Execution**: Event sourcing ensures workflows can recover from any failure
90
+ - **Auto Recovery**: Automatic workflow resumption after worker crashes with event replay
91
+ - **Time Travel**: Sleep for minutes, hours, or days with automatic resumption
92
+ - **Fault Tolerant**: Automatic retries with configurable backoff strategies
93
+ - **Zero-Resource Suspension**: Workflows suspend without holding resources during sleep
94
+ - **Production Ready**: Built on battle-tested Celery and Redis
95
+ - **Fully Typed**: Complete type hints and Pydantic validation
96
+ - **Observable**: Structured logging with workflow context
97
+
98
+ ---
99
+
100
+ ## Quick Start
101
+
102
+ ### Installation
103
+
104
+ **Basic installation** (File and Memory storage backends):
105
+ ```bash
106
+ pip install pyworkflow-engine
107
+ ```
108
+
109
+ **With optional storage backends:**
110
+ ```bash
111
+ # Redis backend (includes Redis as Celery broker)
112
+ pip install pyworkflow-engine[redis]
113
+
114
+ # SQLite backend
115
+ pip install pyworkflow-engine[sqlite]
116
+
117
+ # PostgreSQL backend
118
+ pip install pyworkflow-engine[postgres]
119
+
120
+ # All storage backends
121
+ pip install pyworkflow-engine[all]
122
+
123
+ # Development (includes all backends + dev tools)
124
+ pip install pyworkflow-engine[dev]
125
+ ```
126
+
127
+ ### Prerequisites
128
+
129
+ **For distributed execution** (recommended for production):
130
+
131
+ PyWorkflow uses Celery for distributed task execution. You need a message broker:
132
+
133
+ **Option 1: Redis (recommended)**
134
+ ```bash
135
+ # Install Redis support
136
+ pip install pyworkflow-engine[redis]
137
+
138
+ # Start Redis
139
+ docker run -d -p 6379:6379 redis:7-alpine
140
+
141
+ # Start Celery worker(s)
142
+ celery -A pyworkflow.celery.app worker --loglevel=info
143
+
144
+ # Start Celery Beat (for automatic sleep resumption)
145
+ celery -A pyworkflow.celery.app beat --loglevel=info
146
+ ```
147
+
148
+ Or use the CLI to set up Docker infrastructure:
149
+ ```bash
150
+ pyworkflow setup
151
+ ```
152
+
153
+ **Option 2: Other brokers** (RabbitMQ, etc.)
154
+ ```bash
155
+ # Celery supports multiple brokers
156
+ # Configure via environment: CELERY_BROKER_URL=amqp://localhost
157
+ ```
158
+
159
+ **For local development/testing:**
160
+ ```bash
161
+ # No broker needed - use in-process execution
162
+ pyworkflow configure --runtime local
163
+ ```
164
+
165
+ See [DISTRIBUTED.md](DISTRIBUTED.md) for complete deployment guide.
166
+
167
+ ### Your First Workflow
168
+
169
+ ```python
170
+ from pyworkflow import workflow, step, start, sleep
171
+
172
+ @step()
173
+ async def send_welcome_email(user_id: str):
174
+ # This runs on any available Celery worker
175
+ print(f"Sending welcome email to user {user_id}")
176
+ return f"Email sent to {user_id}"
177
+
178
+ @step()
179
+ async def send_tips_email(user_id: str):
180
+ print(f"Sending tips email to user {user_id}")
181
+ return f"Tips sent to {user_id}"
182
+
183
+ @workflow()
184
+ async def onboarding_workflow(user_id: str):
185
+ # Send welcome email immediately
186
+ await send_welcome_email(user_id)
187
+
188
+ # Sleep for 1 day - workflow suspends, zero resources used
189
+ await sleep("1d")
190
+
191
+ # Automatically resumes after 1 day!
192
+ await send_tips_email(user_id)
193
+
194
+ return "Onboarding complete"
195
+
196
+ # Start workflow - executes across Celery workers
197
+ run_id = start(onboarding_workflow, user_id="user_123")
198
+ print(f"Workflow started: {run_id}")
199
+ ```
200
+
201
+ **What happens:**
202
+ 1. Workflow starts on a Celery worker
203
+ 2. Welcome email is sent
204
+ 3. Workflow suspends after calling `sleep("1d")`
205
+ 4. Worker is freed to handle other tasks
206
+ 5. After 1 day, Celery Beat automatically schedules resumption
207
+ 6. Workflow resumes on any available worker
208
+ 7. Tips email is sent
209
+
210
+ ---
211
+
212
+ ## Core Concepts
213
+
214
+ ### Workflows
215
+
216
+ Workflows are the top-level orchestration functions. They coordinate steps, handle business logic, and can sleep for extended periods.
217
+
218
+ ```python
219
+ from pyworkflow import workflow, start
220
+
221
+ @workflow(name="process_order", max_duration="1h")
222
+ async def process_order(order_id: str):
223
+ """
224
+ Process a customer order.
225
+
226
+ This workflow:
227
+ - Validates the order
228
+ - Processes payment
229
+ - Creates shipment
230
+ - Sends confirmation
231
+ """
232
+ order = await validate_order(order_id)
233
+ payment = await process_payment(order)
234
+ shipment = await create_shipment(order)
235
+ await send_confirmation(order)
236
+
237
+ return {"order_id": order_id, "status": "completed"}
238
+
239
+ # Start the workflow
240
+ run_id = start(process_order, order_id="ORD-123")
241
+ ```
242
+
243
+ ### Steps
244
+
245
+ Steps are the building blocks of workflows. Each step is an isolated, retryable unit of work that runs on Celery workers.
246
+
247
+ ```python
248
+ from pyworkflow import step, RetryableError, FatalError
249
+
250
+ @step(max_retries=5, retry_delay="exponential")
251
+ async def call_external_api(url: str):
252
+ """
253
+ Call external API with automatic retry.
254
+
255
+ Retries up to 5 times with exponential backoff if it fails.
256
+ """
257
+ try:
258
+ response = await httpx.get(url)
259
+
260
+ if response.status_code == 404:
261
+ # Don't retry - resource doesn't exist
262
+ raise FatalError("Resource not found")
263
+
264
+ if response.status_code >= 500:
265
+ # Retry - server error
266
+ raise RetryableError("Server error", retry_after="30s")
267
+
268
+ return response.json()
269
+ except httpx.NetworkError:
270
+ # Retry with exponential backoff
271
+ raise RetryableError("Network error")
272
+ ```
273
+
274
+ ### Sleep and Delays
275
+
276
+ Workflows can sleep for any duration. During sleep, the workflow suspends and consumes zero resources.
277
+
278
+ ```python
279
+ from pyworkflow import workflow, sleep
280
+
281
+ @workflow()
282
+ async def scheduled_reminder(user_id: str):
283
+ # Send immediate reminder
284
+ await send_reminder(user_id, "immediate")
285
+
286
+ # Sleep for 1 hour
287
+ await sleep("1h")
288
+ await send_reminder(user_id, "1 hour later")
289
+
290
+ # Sleep for 1 day
291
+ await sleep("1d")
292
+ await send_reminder(user_id, "1 day later")
293
+
294
+ # Sleep for 1 week
295
+ await sleep("7d")
296
+ await send_reminder(user_id, "1 week later")
297
+
298
+ return "All reminders sent"
299
+ ```
300
+
301
+ **Supported formats:**
302
+ - Duration strings: `"5s"`, `"10m"`, `"2h"`, `"3d"`
303
+ - Timedelta: `timedelta(hours=2, minutes=30)`
304
+ - Datetime: `datetime(2025, 12, 25, 9, 0, 0)`
305
+
306
+ ---
307
+
308
+ ## Architecture
309
+
310
+ ### Event-Sourced Execution
311
+
312
+ PyWorkflow uses event sourcing to achieve durable, fault-tolerant execution:
313
+
314
+ 1. **All state changes are recorded as events** in an append-only log
315
+ 2. **Deterministic replay** enables workflow resumption from any point
316
+ 3. **Complete audit trail** of everything that happened in the workflow
317
+
318
+ **Event Types** (16 total):
319
+ - Workflow: `started`, `completed`, `failed`, `suspended`, `resumed`
320
+ - Step: `started`, `completed`, `failed`, `retrying`
321
+ - Sleep: `created`, `completed`
322
+ - Logging: `info`, `warning`, `error`, `debug`
323
+
324
+ ### Distributed Execution
325
+
326
+ ```
327
+ ┌─────────────────────────────────────────────────────┐
328
+ │ Your Application │
329
+ │ │
330
+ │ start(my_workflow, args) │
331
+ │ │ │
332
+ └─────────┼───────────────────────────────────────────┘
333
+
334
+
335
+ ┌─────────┐
336
+ │ Redis │ ◄──── Message Broker
337
+ └─────────┘
338
+
339
+ ├──────┬──────┬──────┐
340
+ ▼ ▼ ▼ ▼
341
+ ┌──────┐ ┌──────┐ ┌──────┐
342
+ │Worker│ │Worker│ │Worker│ ◄──── Horizontal Scaling
343
+ └──────┘ └──────┘ └──────┘
344
+ │ │ │
345
+ └──────┴──────┘
346
+
347
+
348
+ ┌──────────┐
349
+ │ Storage │ ◄──── Event Log (File/Redis/PostgreSQL)
350
+ └──────────┘
351
+ ```
352
+
353
+ ### Storage Backends
354
+
355
+ PyWorkflow supports pluggable storage backends:
356
+
357
+ | Backend | Status | Installation | Use Case |
358
+ |---------|--------|--------------|----------|
359
+ | **File** | ✅ Complete | Included | Development, single-machine |
360
+ | **Memory** | ✅ Complete | Included | Testing, ephemeral workflows |
361
+ | **SQLite** | ✅ Complete | `pip install pyworkflow-engine[sqlite]` | Embedded, local persistence |
362
+ | **PostgreSQL** | ✅ Complete | `pip install pyworkflow-engine[postgres]` | Production, enterprise |
363
+ | **Redis** | 📋 Planned | `pip install pyworkflow-engine[redis]` | High-performance, distributed |
364
+
365
+ ---
366
+
367
+ ## Advanced Features
368
+
369
+ ### Parallel Execution
370
+
371
+ Use Python's native `asyncio.gather()` for parallel step execution:
372
+
373
+ ```python
374
+ import asyncio
375
+ from pyworkflow import workflow, step
376
+
377
+ @step()
378
+ async def fetch_user(user_id: str):
379
+ # Fetch user data
380
+ return {"id": user_id, "name": "Alice"}
381
+
382
+ @step()
383
+ async def fetch_orders(user_id: str):
384
+ # Fetch user orders
385
+ return [{"id": "ORD-1"}, {"id": "ORD-2"}]
386
+
387
+ @step()
388
+ async def fetch_recommendations(user_id: str):
389
+ # Fetch recommendations
390
+ return ["Product A", "Product B"]
391
+
392
+ @workflow()
393
+ async def dashboard_data(user_id: str):
394
+ # Fetch all data in parallel
395
+ user, orders, recommendations = await asyncio.gather(
396
+ fetch_user(user_id),
397
+ fetch_orders(user_id),
398
+ fetch_recommendations(user_id)
399
+ )
400
+
401
+ return {
402
+ "user": user,
403
+ "orders": orders,
404
+ "recommendations": recommendations
405
+ }
406
+ ```
407
+
408
+ ### Error Handling
409
+
410
+ PyWorkflow distinguishes between retriable and fatal errors:
411
+
412
+ ```python
413
+ from pyworkflow import FatalError, RetryableError, step
414
+
415
+ @step(max_retries=3, retry_delay="exponential")
416
+ async def process_payment(amount: float):
417
+ try:
418
+ # Attempt payment
419
+ result = await payment_gateway.charge(amount)
420
+ return result
421
+ except InsufficientFundsError:
422
+ # Don't retry - user doesn't have enough money
423
+ raise FatalError("Insufficient funds")
424
+ except PaymentGatewayTimeoutError:
425
+ # Retry - temporary issue
426
+ raise RetryableError("Gateway timeout", retry_after="10s")
427
+ except Exception as e:
428
+ # Unknown error - retry with backoff
429
+ raise RetryableError(f"Unknown error: {e}")
430
+ ```
431
+
432
+ **Retry strategies:**
433
+ - `retry_delay="fixed"` - Fixed delay between retries (default: 60s)
434
+ - `retry_delay="exponential"` - Exponential backoff (1s, 2s, 4s, 8s, ...)
435
+ - `retry_delay="5s"` - Custom fixed delay
436
+
437
+ ### Auto Recovery
438
+
439
+ Workflows automatically recover from worker crashes:
440
+
441
+ ```python
442
+ from pyworkflow import workflow, step, sleep
443
+
444
+ @workflow(
445
+ recover_on_worker_loss=True, # Enable recovery (default for durable)
446
+ max_recovery_attempts=5, # Max recovery attempts
447
+ )
448
+ async def resilient_workflow(data_id: str):
449
+ data = await fetch_data(data_id) # Completed steps are skipped on recovery
450
+ await sleep("10m") # Sleep state is preserved
451
+ return await process_data(data) # Continues from here after crash
452
+ ```
453
+
454
+ **What happens on worker crash:**
455
+ 1. Celery detects worker loss, requeues task
456
+ 2. New worker picks up the task
457
+ 3. Events are replayed to restore state
458
+ 4. Workflow resumes from last checkpoint
459
+
460
+ Configure globally:
461
+ ```python
462
+ import pyworkflow
463
+
464
+ pyworkflow.configure(
465
+ default_recover_on_worker_loss=True,
466
+ default_max_recovery_attempts=3,
467
+ )
468
+ ```
469
+
470
+ Or via config file:
471
+ ```yaml
472
+ # pyworkflow.config.yaml
473
+ recovery:
474
+ recover_on_worker_loss: true
475
+ max_recovery_attempts: 3
476
+ ```
477
+
478
+ ### Idempotency
479
+
480
+ Prevent duplicate workflow executions with idempotency keys:
481
+
482
+ ```python
483
+ from pyworkflow import start
484
+
485
+ # Same idempotency key = same workflow
486
+ run_id_1 = start(
487
+ process_order,
488
+ order_id="ORD-123",
489
+ idempotency_key="order-ORD-123"
490
+ )
491
+
492
+ # This will return the same run_id, not start a new workflow
493
+ run_id_2 = start(
494
+ process_order,
495
+ order_id="ORD-123",
496
+ idempotency_key="order-ORD-123"
497
+ )
498
+
499
+ assert run_id_1 == run_id_2 # True!
500
+ ```
501
+
502
+ ### Observability
503
+
504
+ PyWorkflow includes structured logging with automatic context:
505
+
506
+ ```python
507
+ from pyworkflow import configure_logging
508
+
509
+ # Configure logging
510
+ configure_logging(
511
+ level="INFO",
512
+ log_file="workflow.log",
513
+ json_logs=True, # JSON format for production
514
+ show_context=True # Include run_id, step_id, etc.
515
+ )
516
+
517
+ # Logs automatically include:
518
+ # - run_id: Workflow execution ID
519
+ # - workflow_name: Name of the workflow
520
+ # - step_id: Current step ID
521
+ # - step_name: Name of the step
522
+ ```
523
+
524
+ ---
525
+
526
+ ## Testing
527
+
528
+ PyWorkflow uses a unified API for testing with local execution:
529
+
530
+ ```python
531
+ import pytest
532
+ from pyworkflow import workflow, step, start, configure, reset_config
533
+ from pyworkflow.storage.memory import InMemoryStorageBackend
534
+
535
+ @step()
536
+ async def my_step(x: int):
537
+ return x * 2
538
+
539
+ @workflow()
540
+ async def my_workflow(x: int):
541
+ result = await my_step(x)
542
+ return result + 1
543
+
544
+ @pytest.fixture(autouse=True)
545
+ def setup_storage():
546
+ reset_config()
547
+ storage = InMemoryStorageBackend()
548
+ configure(storage=storage, default_durable=True)
549
+ yield storage
550
+ reset_config()
551
+
552
+ @pytest.mark.asyncio
553
+ async def test_my_workflow(setup_storage):
554
+ storage = setup_storage
555
+ run_id = await start(my_workflow, 5)
556
+
557
+ # Get workflow result
558
+ run = await storage.get_run(run_id)
559
+ assert run.status.value == "completed"
560
+ ```
561
+
562
+ ---
563
+
564
+ ## Production Deployment
565
+
566
+ ### Docker Compose
567
+
568
+ ```yaml
569
+ version: '3.8'
570
+
571
+ services:
572
+ redis:
573
+ image: redis:7-alpine
574
+ ports:
575
+ - "6379:6379"
576
+
577
+ worker:
578
+ build: .
579
+ command: celery -A pyworkflow.celery.app worker --loglevel=info
580
+ depends_on:
581
+ - redis
582
+ deploy:
583
+ replicas: 3 # Run 3 workers
584
+
585
+ beat:
586
+ build: .
587
+ command: celery -A pyworkflow.celery.app beat --loglevel=info
588
+ depends_on:
589
+ - redis
590
+
591
+ flower:
592
+ build: .
593
+ command: celery -A pyworkflow.celery.app flower --port=5555
594
+ ports:
595
+ - "5555:5555"
596
+ ```
597
+
598
+ Start everything using the CLI:
599
+ ```bash
600
+ pyworkflow setup
601
+ ```
602
+
603
+ See [DISTRIBUTED.md](DISTRIBUTED.md) for complete deployment guide with Kubernetes.
604
+
605
+ ---
606
+
607
+ ## Examples
608
+
609
+ Check out the [examples/](examples/) directory for complete working examples:
610
+
611
+ - **[basic_workflow.py](examples/functional/basic_workflow.py)** - Complete example with retries, errors, and sleep
612
+ - **[distributed_example.py](examples/functional/distributed_example.py)** - Multi-worker distributed execution example
613
+
614
+ ---
615
+
616
+ ## Project Status
617
+
618
+ ✅ **Status**: Production Ready (v1.0)
619
+
620
+ **Completed Features**:
621
+ - ✅ Core workflow and step execution
622
+ - ✅ Event sourcing with 16 event types
623
+ - ✅ Distributed execution via Celery
624
+ - ✅ Sleep primitive with automatic resumption
625
+ - ✅ Error handling and retry strategies
626
+ - ✅ File storage backend
627
+ - ✅ Structured logging
628
+ - ✅ Comprehensive test coverage (68 tests)
629
+ - ✅ Docker Compose deployment
630
+ - ✅ Idempotency support
631
+
632
+ **Next Milestones**:
633
+ - 📋 Redis storage backend
634
+ - 📋 PostgreSQL storage backend
635
+ - 📋 Webhook integration
636
+ - 📋 Web UI for monitoring
637
+ - 📋 CLI management tools
638
+
639
+ ---
640
+
641
+ ## Contributing
642
+
643
+ Contributions are welcome!
644
+
645
+ ### Development Setup
646
+
647
+ ```bash
648
+ # Clone repository
649
+ git clone https://github.com/QualityUnit/pyworkflow
650
+ cd pyworkflow
651
+
652
+ # Install with Poetry
653
+ poetry install
654
+
655
+ # Run tests
656
+ poetry run pytest
657
+
658
+ # Format code
659
+ poetry run black pyworkflow tests
660
+ poetry run ruff check pyworkflow tests
661
+
662
+ # Type checking
663
+ poetry run mypy pyworkflow
664
+ ```
665
+
666
+ ---
667
+
668
+ ## Documentation
669
+
670
+ - **[Distributed Deployment Guide](DISTRIBUTED.md)** - Production deployment with Docker Compose and Kubernetes
671
+ - [Examples](examples/) - Working examples and patterns
672
+ - [API Reference](docs/api-reference.md) (Coming soon)
673
+ - [Architecture Guide](docs/architecture.md) (Coming soon)
674
+
675
+ ---
676
+
677
+ ## License
678
+
679
+ Apache License 2.0 - See [LICENSE](LICENSE) file for details.
680
+
681
+ ---
682
+
683
+ ## Links
684
+
685
+ - **Documentation**: https://docs.pyworkflow.dev
686
+ - **GitHub**: https://github.com/QualityUnit/pyworkflow
687
+ - **Issues**: https://github.com/QualityUnit/pyworkflow/issues