|
| 1 | +""" |
| 2 | +Workflow Example - Durable multi-step execution. |
| 3 | +
|
| 4 | +Control flow: |
| 5 | + ctx.next(payload) → advance to next block |
| 6 | + ctx.repeat() → re-run current block |
| 7 | + normal return → workflow completes |
| 8 | +""" |
| 9 | + |
| 10 | +from fastloop import FastLoop, LoopContext, LoopEvent, WorkflowBlock |
| 11 | + |
| 12 | +app = FastLoop(name="workflow-demo") |
| 13 | + |
| 14 | + |
| 15 | +# --- Events --- |
| 16 | + |
| 17 | + |
| 18 | +@app.event("start_workflow") |
| 19 | +class StartWorkflow(LoopEvent): |
| 20 | + pass |
| 21 | + |
| 22 | + |
| 23 | +@app.event("user_input") |
| 24 | +class UserInput(LoopEvent): |
| 25 | + value: str |
| 26 | + |
| 27 | + |
| 28 | +# --- Callbacks (optional) --- |
| 29 | + |
| 30 | + |
| 31 | +def on_block_done(ctx: LoopContext, block: WorkflowBlock, payload: dict | None): |
| 32 | + print(f" ✓ Block complete: {block.type}") |
| 33 | + |
| 34 | + |
| 35 | +def on_error(ctx: LoopContext, block: WorkflowBlock, error: Exception): |
| 36 | + print(f" ✗ Block error: {block.type} - {error}") |
| 37 | + |
| 38 | + |
| 39 | +# --- Workflow --- |
| 40 | + |
| 41 | + |
| 42 | +@app.workflow( |
| 43 | + name="onboarding", |
| 44 | + start_event=StartWorkflow, |
| 45 | + on_block_complete=on_block_done, |
| 46 | + on_error=on_error, |
| 47 | +) |
| 48 | +async def onboarding_workflow( |
| 49 | + ctx: LoopContext, |
| 50 | + blocks: list[WorkflowBlock], |
| 51 | + current_block: WorkflowBlock, |
| 52 | +): |
| 53 | + """ |
| 54 | + Multi-step onboarding. State survives restarts. |
| 55 | +
|
| 56 | + ctx.block_index - current position (0-indexed) |
| 57 | + ctx.block_count - total blocks |
| 58 | + ctx.previous_payload - data from previous ctx.next(payload) |
| 59 | + """ |
| 60 | + print(f"[{ctx.block_index + 1}/{ctx.block_count}] {current_block.type}") |
| 61 | + |
| 62 | + match current_block.type: |
| 63 | + case "collect_name": |
| 64 | + response = await ctx.wait_for( |
| 65 | + UserInput, timeout=300.0, raise_on_timeout=False |
| 66 | + ) |
| 67 | + if response: |
| 68 | + await ctx.set("user_name", response.value) |
| 69 | + ctx.next() |
| 70 | + else: |
| 71 | + ctx.repeat() |
| 72 | + |
| 73 | + case "collect_email": |
| 74 | + response = await ctx.wait_for( |
| 75 | + UserInput, timeout=300.0, raise_on_timeout=False |
| 76 | + ) |
| 77 | + if response: |
| 78 | + await ctx.set("user_email", response.value) |
| 79 | + ctx.next() |
| 80 | + else: |
| 81 | + ctx.repeat() |
| 82 | + |
| 83 | + case "confirm": |
| 84 | + name = await ctx.get("user_name") |
| 85 | + email = await ctx.get("user_email") |
| 86 | + print(f"Done: {name} <{email}>") |
| 87 | + |
| 88 | + |
| 89 | +if __name__ == "__main__": |
| 90 | + app.run(port=8111) |
| 91 | + |
| 92 | +# Example usage: |
| 93 | +# |
| 94 | +# 1. Start workflow: |
| 95 | +# curl -X POST http://localhost:8111/onboarding \ |
| 96 | +# -H "Content-Type: application/json" \ |
| 97 | +# -d '{ |
| 98 | +# "type": "start_workflow", |
| 99 | +# "blocks": [ |
| 100 | +# {"type": "collect_name", "text": "Please enter your name"}, |
| 101 | +# {"type": "collect_email", "text": "Please enter your email"}, |
| 102 | +# {"type": "confirm", "text": "Confirm your details"} |
| 103 | +# ] |
| 104 | +# }' |
| 105 | +# |
| 106 | +# 2. Send event to workflow (use workflow_id from response): |
| 107 | +# curl -X POST http://localhost:8111/onboarding/<workflow_id>/event \ |
| 108 | +# -H "Content-Type: application/json" \ |
| 109 | +# -d '{"type": "user_input", "value": "John Doe", "workflow_id": "<id>"}' |
| 110 | +# |
| 111 | +# 3. Check workflow status: |
| 112 | +# curl http://localhost:8111/onboarding/<workflow_id> |
| 113 | +# |
| 114 | +# 4. If service restarts, workflow resumes from current block automatically |
0 commit comments