loom-core 0.1.0__tar.gz
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.
- loom_core-0.1.0/LICENSE +21 -0
- loom_core-0.1.0/MANIFEST.in +8 -0
- loom_core-0.1.0/PKG-INFO +342 -0
- loom_core-0.1.0/QUICKSTART.md +147 -0
- loom_core-0.1.0/README.md +303 -0
- loom_core-0.1.0/loom_core.egg-info/PKG-INFO +342 -0
- loom_core-0.1.0/loom_core.egg-info/SOURCES.txt +56 -0
- loom_core-0.1.0/loom_core.egg-info/dependency_links.txt +1 -0
- loom_core-0.1.0/loom_core.egg-info/entry_points.txt +2 -0
- loom_core-0.1.0/loom_core.egg-info/requires.txt +12 -0
- loom_core-0.1.0/loom_core.egg-info/top_level.txt +1 -0
- loom_core-0.1.0/pyproject.toml +88 -0
- loom_core-0.1.0/setup.cfg +4 -0
- loom_core-0.1.0/setup.py +52 -0
- loom_core-0.1.0/src/__init__.py +45 -0
- loom_core-0.1.0/src/cli/__init__.py +5 -0
- loom_core-0.1.0/src/cli/cli.py +246 -0
- loom_core-0.1.0/src/common/activity.py +30 -0
- loom_core-0.1.0/src/common/config.py +9 -0
- loom_core-0.1.0/src/common/errors.py +64 -0
- loom_core-0.1.0/src/common/workflow.py +56 -0
- loom_core-0.1.0/src/core/__init__.py +0 -0
- loom_core-0.1.0/src/core/compiled.py +41 -0
- loom_core-0.1.0/src/core/context.py +256 -0
- loom_core-0.1.0/src/core/engine.py +106 -0
- loom_core-0.1.0/src/core/handle.py +166 -0
- loom_core-0.1.0/src/core/logger.py +60 -0
- loom_core-0.1.0/src/core/runner.py +53 -0
- loom_core-0.1.0/src/core/state.py +96 -0
- loom_core-0.1.0/src/core/worker.py +147 -0
- loom_core-0.1.0/src/core/workflow.py +168 -0
- loom_core-0.1.0/src/database/__init__.py +0 -0
- loom_core-0.1.0/src/database/db.py +716 -0
- loom_core-0.1.0/src/decorators/__init__.py +0 -0
- loom_core-0.1.0/src/decorators/activity.py +126 -0
- loom_core-0.1.0/src/decorators/workflow.py +46 -0
- loom_core-0.1.0/src/lib/progress.py +109 -0
- loom_core-0.1.0/src/lib/utils.py +25 -0
- loom_core-0.1.0/src/migrations/down/001_setup_pragma.sql +5 -0
- loom_core-0.1.0/src/migrations/down/002_create_workflows.sql +3 -0
- loom_core-0.1.0/src/migrations/down/003.create_events.sql +3 -0
- loom_core-0.1.0/src/migrations/down/004.create_tasks.sql +3 -0
- loom_core-0.1.0/src/migrations/down/005.create_indexes.sql +5 -0
- loom_core-0.1.0/src/migrations/down/006_auto_update_triggers.sql +4 -0
- loom_core-0.1.0/src/migrations/down/007_create_logs.sql +1 -0
- loom_core-0.1.0/src/migrations/up/001_setup_pragma.sql +11 -0
- loom_core-0.1.0/src/migrations/up/002_create_workflows.sql +15 -0
- loom_core-0.1.0/src/migrations/up/003_create_events.sql +13 -0
- loom_core-0.1.0/src/migrations/up/004_create_tasks.sql +23 -0
- loom_core-0.1.0/src/migrations/up/005_create_indexes.sql +11 -0
- loom_core-0.1.0/src/migrations/up/006_auto_update_triggers.sql +19 -0
- loom_core-0.1.0/src/migrations/up/007_create_logs.sql +10 -0
- loom_core-0.1.0/src/schemas/__init__.py +0 -0
- loom_core-0.1.0/src/schemas/activity.py +13 -0
- loom_core-0.1.0/src/schemas/database.py +17 -0
- loom_core-0.1.0/src/schemas/events.py +70 -0
- loom_core-0.1.0/src/schemas/tasks.py +58 -0
- loom_core-0.1.0/src/schemas/workflow.py +33 -0
loom_core-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Loom Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
loom_core-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loom-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Durable workflow orchestration engine for Python
|
|
5
|
+
Home-page: https://github.com/satadeep3927/loom
|
|
6
|
+
Author: Satadeep Dasgupta
|
|
7
|
+
Author-email: Satadeep Dasgupta <satadeep.dasgupta@brainiuminfotech.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/satadeep3927/loom
|
|
10
|
+
Project-URL: Documentation, https://github.com/satadeep3927/loom/blob/main/README.md
|
|
11
|
+
Project-URL: Repository, https://github.com/satadeep3927/loom
|
|
12
|
+
Project-URL: Issues, https://github.com/satadeep3927/loom/issues
|
|
13
|
+
Keywords: workflow,orchestration,durable,event-sourcing,temporal
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Framework :: AsyncIO
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: aiosqlite>=0.19.0
|
|
25
|
+
Requires-Dist: click>=8.0.0
|
|
26
|
+
Requires-Dist: rich>=13.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
35
|
+
Dynamic: author
|
|
36
|
+
Dynamic: home-page
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
|
|
40
|
+
# Loom - Durable Workflow Orchestration
|
|
41
|
+
|
|
42
|
+
[](https://www.python.org/downloads/)
|
|
43
|
+
[](https://opensource.org/licenses/MIT)
|
|
44
|
+
[](https://pypi.org/project/loom-core/)
|
|
45
|
+
|
|
46
|
+
A Python-based durable workflow orchestration engine inspired by [Temporal](https://temporal.io/) and [Durable Task Framework](https://github.com/Azure/durabletask). Loom provides event-sourced, deterministic workflow execution with automatic recovery and replay capabilities.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **Event Sourcing**: All workflow state changes persisted as immutable events
|
|
51
|
+
- **Deterministic Replay**: Workflows reconstruct from event history for recovery
|
|
52
|
+
- **Type Safe**: Full generic typing support with `Workflow[InputT, StateT]`
|
|
53
|
+
- **Async First**: Built on asyncio for high-performance concurrent execution
|
|
54
|
+
- **Durable Execution**: Workflows survive process crashes and auto-recover
|
|
55
|
+
- **Beautiful CLI**: Rich console interface with progress tracking
|
|
56
|
+
- **Well Tested**: Comprehensive test suite with pytest
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
### Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install loom-core
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or install from source:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/yourusername/loom.git
|
|
70
|
+
cd loom
|
|
71
|
+
pip install -e .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Define a Workflow
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import asyncio
|
|
78
|
+
from typing import TypedDict
|
|
79
|
+
import loom
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Define your data types
|
|
83
|
+
class OrderInput(TypedDict):
|
|
84
|
+
order_id: str
|
|
85
|
+
customer_email: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class OrderState(TypedDict):
|
|
89
|
+
payment_confirmed: bool
|
|
90
|
+
email_sent: bool
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Define activities (side effects)
|
|
94
|
+
@loom.activity(name="process_payment", retry_count=3, timeout_seconds=30)
|
|
95
|
+
async def process_payment(order_id: str) -> bool:
|
|
96
|
+
# Call payment API
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@loom.activity(name="send_email", retry_count=2)
|
|
101
|
+
async def send_confirmation_email(email: str, order_id: str) -> None:
|
|
102
|
+
# Send email via service
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Define workflow
|
|
107
|
+
@loom.workflow(name="OrderProcessing", version="1.0.0")
|
|
108
|
+
class OrderWorkflow(loom.Workflow[OrderInput, OrderState]):
|
|
109
|
+
|
|
110
|
+
@loom.step(name="process_payment")
|
|
111
|
+
async def payment_step(self, ctx: loom.WorkflowContext[OrderInput, OrderState]):
|
|
112
|
+
success = await ctx.activity(process_payment, ctx.input["order_id"])
|
|
113
|
+
await ctx.state.set("payment_confirmed", success)
|
|
114
|
+
ctx.logger.info(f"Payment processed: {success}")
|
|
115
|
+
|
|
116
|
+
@loom.step(name="send_confirmation")
|
|
117
|
+
async def notification_step(self, ctx: loom.WorkflowContext[OrderInput, OrderState]):
|
|
118
|
+
if ctx.state["payment_confirmed"]:
|
|
119
|
+
await ctx.activity(
|
|
120
|
+
send_confirmation_email,
|
|
121
|
+
ctx.input["customer_email"],
|
|
122
|
+
ctx.input["order_id"]
|
|
123
|
+
)
|
|
124
|
+
await ctx.state.set("email_sent", True)
|
|
125
|
+
ctx.logger.info("Confirmation email sent")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Note**: For state updates, use:
|
|
129
|
+
- `await ctx.state.set("key", value)` for single values
|
|
130
|
+
- `await ctx.state.update(key=lambda _: asyncio.sleep(0, value))` for batch updates (requires awaitable)
|
|
131
|
+
|
|
132
|
+
See [STATE_MANAGEMENT.md](STATE_MANAGEMENT.md) for detailed examples.
|
|
133
|
+
|
|
134
|
+
### Start a Workflow
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
async def main():
|
|
138
|
+
db = loom.Database()
|
|
139
|
+
async with db:
|
|
140
|
+
# Initialize database
|
|
141
|
+
await db.migrate_up()
|
|
142
|
+
|
|
143
|
+
# Start workflow
|
|
144
|
+
handle = await db.start_workflow(
|
|
145
|
+
OrderWorkflow,
|
|
146
|
+
workflow_input=OrderInput(
|
|
147
|
+
order_id="ORD-12345",
|
|
148
|
+
customer_email="customer@example.com"
|
|
149
|
+
),
|
|
150
|
+
initial_state=OrderState(
|
|
151
|
+
payment_confirmed=False,
|
|
152
|
+
email_sent=False
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
print(f"Workflow started: {handle.workflow_id}")
|
|
157
|
+
|
|
158
|
+
# Execute workflow tasks
|
|
159
|
+
while True:
|
|
160
|
+
task_executed = await loom.run_once()
|
|
161
|
+
if not task_executed:
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
asyncio.run(main())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Run the Worker
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Initialize database
|
|
173
|
+
loom init
|
|
174
|
+
|
|
175
|
+
# Start worker with 4 concurrent task processors
|
|
176
|
+
loom worker
|
|
177
|
+
|
|
178
|
+
# Custom configuration
|
|
179
|
+
loom worker --workers 8 --poll-interval 1.0
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## CLI Commands
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Initialize database
|
|
186
|
+
loom init
|
|
187
|
+
|
|
188
|
+
# Start distributed worker
|
|
189
|
+
loom worker [--workers 4] [--poll-interval 0.5]
|
|
190
|
+
|
|
191
|
+
# List workflows
|
|
192
|
+
loom list [--limit 50] [--status RUNNING]
|
|
193
|
+
|
|
194
|
+
# Inspect workflow details
|
|
195
|
+
loom inspect <workflow-id> [--events]
|
|
196
|
+
|
|
197
|
+
# Show database statistics
|
|
198
|
+
loom stats
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 🏗️ Architecture
|
|
202
|
+
|
|
203
|
+
### Core Components
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
┌─────────────────────────────────────────────────────────┐
|
|
207
|
+
│ Workflow │
|
|
208
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
209
|
+
│ │ Step 1 │→ │ Step 2 │→ │ Step 3 │ │
|
|
210
|
+
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
211
|
+
└─────────────────────────────────────────────────────────┘
|
|
212
|
+
↓
|
|
213
|
+
┌─────────────────────────────────────────────────────────┐
|
|
214
|
+
│ WorkflowContext │
|
|
215
|
+
│ • State Management (StateProxy) │
|
|
216
|
+
│ • Activity Execution │
|
|
217
|
+
│ • Event Replay & Cursor │
|
|
218
|
+
│ • Logger (replay-safe) │
|
|
219
|
+
└─────────────────────────────────────────────────────────┘
|
|
220
|
+
↓
|
|
221
|
+
┌─────────────────────────────────────────────────────────┐
|
|
222
|
+
│ Engine │
|
|
223
|
+
│ • replay_until_block() - Step execution │
|
|
224
|
+
│ • replay_activity() - Activity retry │
|
|
225
|
+
│ • Event matching & determinism │
|
|
226
|
+
└─────────────────────────────────────────────────────────┘
|
|
227
|
+
↓
|
|
228
|
+
┌─────────────────────────────────────────────────────────┐
|
|
229
|
+
│ Database (SQLite) │
|
|
230
|
+
│ • workflows • events • tasks • logs │
|
|
231
|
+
└─────────────────────────────────────────────────────────┘
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Event Types
|
|
235
|
+
|
|
236
|
+
- `WORKFLOW_STARTED` - Workflow initialization
|
|
237
|
+
- `WORKFLOW_COMPLETED` - Successful completion
|
|
238
|
+
- `WORKFLOW_FAILED` - Fatal error occurred
|
|
239
|
+
- `STATE_SET` - Single state key updated
|
|
240
|
+
- `STATE_UPDATE` - Batch state update
|
|
241
|
+
- `ACTIVITY_SCHEDULED` - Activity queued for execution
|
|
242
|
+
- `ACTIVITY_COMPLETED` - Activity finished successfully
|
|
243
|
+
- `ACTIVITY_FAILED` - Activity permanently failed
|
|
244
|
+
- `TIMER_FIRED` - Sleep/delay completed
|
|
245
|
+
- `SIGNAL_RECEIVED` - External signal received
|
|
246
|
+
|
|
247
|
+
## 📚 Documentation
|
|
248
|
+
|
|
249
|
+
See [`.copilot-instructions.md`](.copilot-instructions.md) for comprehensive development guidelines including:
|
|
250
|
+
|
|
251
|
+
- Event sourcing patterns
|
|
252
|
+
- Deterministic execution rules
|
|
253
|
+
- Activity best practices
|
|
254
|
+
- Testing strategies
|
|
255
|
+
- Common pitfalls to avoid
|
|
256
|
+
|
|
257
|
+
## 🧪 Testing
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# Run all tests
|
|
261
|
+
pytest
|
|
262
|
+
|
|
263
|
+
# Run with coverage
|
|
264
|
+
pytest --cov=src --cov-report=html
|
|
265
|
+
|
|
266
|
+
# Run specific test file
|
|
267
|
+
pytest tests/test_workflow.py
|
|
268
|
+
|
|
269
|
+
# Verbose output
|
|
270
|
+
pytest -v
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Project Structure
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
loom/
|
|
277
|
+
├── src/
|
|
278
|
+
│ ├── common/ # Shared utilities
|
|
279
|
+
│ ├── core/ # Core engine (context, engine, runner, worker)
|
|
280
|
+
│ ├── database/ # Database layer
|
|
281
|
+
│ ├── decorators/ # @workflow, @step, @activity
|
|
282
|
+
│ ├── lib/ # Utilities and progress tracking
|
|
283
|
+
│ ├── migrations/ # Database migrations
|
|
284
|
+
│ └── schemas/ # Type definitions
|
|
285
|
+
├── tests/ # Test suite
|
|
286
|
+
├── examples/ # Example workflows
|
|
287
|
+
├── loom.py # Main package interface
|
|
288
|
+
└── pyproject.toml # Package configuration
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Configuration
|
|
292
|
+
|
|
293
|
+
Loom uses SQLite by default for simplicity. For production:
|
|
294
|
+
|
|
295
|
+
- Consider PostgreSQL/MySQL for scalability
|
|
296
|
+
- Implement connection pooling
|
|
297
|
+
- Add monitoring and alerting
|
|
298
|
+
- Deploy multiple workers for high availability
|
|
299
|
+
|
|
300
|
+
## Contributing
|
|
301
|
+
|
|
302
|
+
Contributions welcome! Please ensure:
|
|
303
|
+
|
|
304
|
+
1. Tests pass: `pytest`
|
|
305
|
+
2. Code formatted: `black .`
|
|
306
|
+
3. Type checking: `mypy .`
|
|
307
|
+
4. Linting: `ruff check .`
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT License - see LICENSE file for details
|
|
312
|
+
|
|
313
|
+
## Acknowledgments
|
|
314
|
+
|
|
315
|
+
Inspired by:
|
|
316
|
+
- [Temporal](https://temporal.io/) - The workflow orchestration platform
|
|
317
|
+
- [Durable Task Framework](https://github.com/Azure/durabletask) - Microsoft's durable task library
|
|
318
|
+
- [Cadence](https://cadenceworkflow.io/) - Uber's workflow platform
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
**Built withpy src`
|
|
323
|
+
4. Linting: `ruff check src`
|
|
324
|
+
|
|
325
|
+
## 📝 License
|
|
326
|
+
|
|
327
|
+
MIT License - see LICENSE file for details
|
|
328
|
+
|
|
329
|
+
## 🙏 Acknowledgments
|
|
330
|
+
|
|
331
|
+
Inspired by:
|
|
332
|
+
- [Temporal](https://temporal.io/) - The workflow orchestration platform
|
|
333
|
+
- [Durable Task Framework](https://github.com/Azure/durabletask) - Microsoft's durable task library
|
|
334
|
+
- [Cadence](https://cadenceworkflow.io/) - Uber's workflow platform
|
|
335
|
+
|
|
336
|
+
## 📧 Contact
|
|
337
|
+
|
|
338
|
+
For questions and support, please open an issue on GitHub.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
**Built with ❤️ using Python 3.12+**
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
This guide will help you get started with Loom in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
pip install -r requirements.txt
|
|
10
|
+
|
|
11
|
+
# Initialize the database
|
|
12
|
+
python loom_cli.py init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Your First Workflow
|
|
16
|
+
|
|
17
|
+
Create a file `my_first_workflow.py`:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from src.core.context import WorkflowContext
|
|
22
|
+
from src.core.workflow import Workflow
|
|
23
|
+
from src.decorators.workflow import workflow, step
|
|
24
|
+
from src.decorators.activity import activity
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# 1. Define your data types
|
|
28
|
+
@dataclass
|
|
29
|
+
class HelloInput:
|
|
30
|
+
name: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class HelloState:
|
|
35
|
+
greeting: str = ""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# 2. Define an activity (for side effects)
|
|
39
|
+
@activity(name="get_greeting")
|
|
40
|
+
async def get_greeting(name: str) -> str:
|
|
41
|
+
return f"Hello, {name}!"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# 3. Define your workflow
|
|
45
|
+
@workflow(name="HelloWorkflow", version="1.0.0")
|
|
46
|
+
class HelloWorkflow(Workflow[HelloInput, HelloState]):
|
|
47
|
+
|
|
48
|
+
@step(name="greet")
|
|
49
|
+
async def greet_step(self, ctx: WorkflowContext[HelloInput, HelloState]):
|
|
50
|
+
# Call activity
|
|
51
|
+
greeting = await ctx.activity(get_greeting, ctx.input.name)
|
|
52
|
+
|
|
53
|
+
# Update state
|
|
54
|
+
await ctx.state.set("greeting", greeting)
|
|
55
|
+
|
|
56
|
+
# Log
|
|
57
|
+
ctx.logger.info(f"Generated greeting: {greeting}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# 4. Start the workflow
|
|
61
|
+
async def main():
|
|
62
|
+
import asyncio
|
|
63
|
+
|
|
64
|
+
workflow = HelloWorkflow.compile()
|
|
65
|
+
handle = await workflow.start(input=HelloInput(name="World"))
|
|
66
|
+
|
|
67
|
+
print(f"Workflow started: {handle.id}")
|
|
68
|
+
print(f"Status: {await handle.status()}")
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
import asyncio
|
|
72
|
+
asyncio.run(main())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Run the Worker
|
|
76
|
+
|
|
77
|
+
In a separate terminal:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python loom_cli.py worker
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Execute Your Workflow
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python my_first_workflow.py
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Check Workflow Status
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# List all workflows
|
|
93
|
+
python loom_cli.py list
|
|
94
|
+
|
|
95
|
+
# Inspect specific workflow
|
|
96
|
+
python loom_cli.py inspect <workflow-id>
|
|
97
|
+
|
|
98
|
+
# Show database stats
|
|
99
|
+
python loom_cli.py stats
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Next Steps
|
|
103
|
+
|
|
104
|
+
- Read the [README.md](README.md) for architecture details
|
|
105
|
+
- Check [examples/](examples/) for more complex workflows
|
|
106
|
+
- Review [.copilot-instructions.md](.copilot-instructions.md) for best practices
|
|
107
|
+
- Run tests: `pytest`
|
|
108
|
+
|
|
109
|
+
## Common Patterns
|
|
110
|
+
|
|
111
|
+
### Retry Failed Activities
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
@activity(name="api_call", retry_count=3, timeout_seconds=30)
|
|
115
|
+
async def call_external_api(url: str) -> dict:
|
|
116
|
+
# This will retry up to 3 times on failure
|
|
117
|
+
async with httpx.AsyncClient() as client:
|
|
118
|
+
response = await client.get(url)
|
|
119
|
+
response.raise_for_status()
|
|
120
|
+
return response.json()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Sleep/Delay in Workflows
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from datetime import timedelta
|
|
127
|
+
|
|
128
|
+
@step()
|
|
129
|
+
async def delayed_step(self, ctx: WorkflowContext):
|
|
130
|
+
# Sleep for 5 minutes (durable - survives process restart)
|
|
131
|
+
await ctx.sleep(timedelta(minutes=5))
|
|
132
|
+
ctx.logger.info("Woke up after 5 minutes!")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Update Multiple State Fields
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
@step()
|
|
139
|
+
async def batch_update(self, ctx: WorkflowContext):
|
|
140
|
+
await ctx.state.update({
|
|
141
|
+
"processed": True,
|
|
142
|
+
"count": 42,
|
|
143
|
+
"timestamp": "2024-01-28T10:00:00Z"
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Happy workflow orchestrating! 🧵
|