stabilize 0.9.2__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.
- stabilize/__init__.py +29 -0
- stabilize/cli.py +1193 -0
- stabilize/context/__init__.py +7 -0
- stabilize/context/stage_context.py +170 -0
- stabilize/dag/__init__.py +15 -0
- stabilize/dag/graph.py +215 -0
- stabilize/dag/topological.py +199 -0
- stabilize/examples/__init__.py +1 -0
- stabilize/examples/docker-example.py +759 -0
- stabilize/examples/golden-standard-expected-result.txt +1 -0
- stabilize/examples/golden-standard.py +488 -0
- stabilize/examples/http-example.py +606 -0
- stabilize/examples/llama-example.py +662 -0
- stabilize/examples/python-example.py +731 -0
- stabilize/examples/shell-example.py +399 -0
- stabilize/examples/ssh-example.py +603 -0
- stabilize/handlers/__init__.py +53 -0
- stabilize/handlers/base.py +226 -0
- stabilize/handlers/complete_stage.py +209 -0
- stabilize/handlers/complete_task.py +75 -0
- stabilize/handlers/complete_workflow.py +150 -0
- stabilize/handlers/run_task.py +369 -0
- stabilize/handlers/start_stage.py +262 -0
- stabilize/handlers/start_task.py +74 -0
- stabilize/handlers/start_workflow.py +136 -0
- stabilize/launcher.py +307 -0
- stabilize/migrations/01KDQ4N9QPJ6Q4MCV3V9GHWPV4_initial_schema.sql +97 -0
- stabilize/migrations/01KDRK3TXW4R2GERC1WBCQYJGG_rag_embeddings.sql +25 -0
- stabilize/migrations/__init__.py +1 -0
- stabilize/models/__init__.py +15 -0
- stabilize/models/stage.py +389 -0
- stabilize/models/status.py +146 -0
- stabilize/models/task.py +125 -0
- stabilize/models/workflow.py +317 -0
- stabilize/orchestrator.py +113 -0
- stabilize/persistence/__init__.py +28 -0
- stabilize/persistence/connection.py +185 -0
- stabilize/persistence/factory.py +136 -0
- stabilize/persistence/memory.py +214 -0
- stabilize/persistence/postgres.py +655 -0
- stabilize/persistence/sqlite.py +674 -0
- stabilize/persistence/store.py +235 -0
- stabilize/queue/__init__.py +59 -0
- stabilize/queue/messages.py +377 -0
- stabilize/queue/processor.py +312 -0
- stabilize/queue/queue.py +526 -0
- stabilize/queue/sqlite_queue.py +354 -0
- stabilize/rag/__init__.py +19 -0
- stabilize/rag/assistant.py +459 -0
- stabilize/rag/cache.py +294 -0
- stabilize/stages/__init__.py +11 -0
- stabilize/stages/builder.py +253 -0
- stabilize/tasks/__init__.py +19 -0
- stabilize/tasks/interface.py +335 -0
- stabilize/tasks/registry.py +255 -0
- stabilize/tasks/result.py +283 -0
- stabilize-0.9.2.dist-info/METADATA +301 -0
- stabilize-0.9.2.dist-info/RECORD +61 -0
- stabilize-0.9.2.dist-info/WHEEL +4 -0
- stabilize-0.9.2.dist-info/entry_points.txt +2 -0
- stabilize-0.9.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HTTP Example - Demonstrates making HTTP requests with Stabilize.
|
|
4
|
+
|
|
5
|
+
This example shows how to:
|
|
6
|
+
1. Create a custom Task that makes HTTP requests
|
|
7
|
+
2. Support all HTTP methods (GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH)
|
|
8
|
+
3. Build workflows with API interactions
|
|
9
|
+
|
|
10
|
+
Requirements:
|
|
11
|
+
None (uses urllib from standard library)
|
|
12
|
+
|
|
13
|
+
Run with:
|
|
14
|
+
python examples/http-example.py
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import ssl
|
|
20
|
+
import time
|
|
21
|
+
import urllib.error
|
|
22
|
+
import urllib.request
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(level=logging.ERROR)
|
|
26
|
+
|
|
27
|
+
from stabilize import StageExecution, TaskExecution, Workflow
|
|
28
|
+
from stabilize.handlers.complete_stage import CompleteStageHandler
|
|
29
|
+
from stabilize.handlers.complete_task import CompleteTaskHandler
|
|
30
|
+
from stabilize.handlers.complete_workflow import CompleteWorkflowHandler
|
|
31
|
+
from stabilize.handlers.run_task import RunTaskHandler
|
|
32
|
+
from stabilize.handlers.start_stage import StartStageHandler
|
|
33
|
+
from stabilize.handlers.start_task import StartTaskHandler
|
|
34
|
+
from stabilize.handlers.start_workflow import StartWorkflowHandler
|
|
35
|
+
from stabilize.orchestrator import Orchestrator
|
|
36
|
+
from stabilize.persistence.sqlite import SqliteWorkflowStore
|
|
37
|
+
from stabilize.persistence.store import WorkflowStore
|
|
38
|
+
from stabilize.queue.processor import QueueProcessor
|
|
39
|
+
from stabilize.queue.queue import Queue
|
|
40
|
+
from stabilize.queue.sqlite_queue import SqliteQueue
|
|
41
|
+
from stabilize.tasks.interface import Task
|
|
42
|
+
from stabilize.tasks.registry import TaskRegistry
|
|
43
|
+
from stabilize.tasks.result import TaskResult
|
|
44
|
+
|
|
45
|
+
# =============================================================================
|
|
46
|
+
# Custom Task: HTTPTask
|
|
47
|
+
# =============================================================================
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class HTTPTask(Task):
|
|
51
|
+
"""
|
|
52
|
+
Make HTTP requests.
|
|
53
|
+
|
|
54
|
+
Context Parameters:
|
|
55
|
+
url: Request URL (required)
|
|
56
|
+
method: HTTP method - GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH (default: GET)
|
|
57
|
+
headers: Request headers as dict (optional)
|
|
58
|
+
body: Request body as string (optional)
|
|
59
|
+
json_body: Request body as dict, auto-serialized to JSON (optional)
|
|
60
|
+
timeout: Request timeout in seconds (default: 30)
|
|
61
|
+
verify_ssl: Verify SSL certificates (default: True)
|
|
62
|
+
expected_status: Expected status code, fails if mismatch (optional)
|
|
63
|
+
|
|
64
|
+
Outputs:
|
|
65
|
+
status_code: HTTP status code
|
|
66
|
+
headers: Response headers as dict
|
|
67
|
+
body: Response body as string
|
|
68
|
+
elapsed_ms: Request duration in milliseconds
|
|
69
|
+
url: Final URL (after redirects)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
SUPPORTED_METHODS = {"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"}
|
|
73
|
+
|
|
74
|
+
def execute(self, stage: StageExecution) -> TaskResult:
|
|
75
|
+
url = stage.context.get("url")
|
|
76
|
+
method = stage.context.get("method", "GET").upper()
|
|
77
|
+
headers = stage.context.get("headers", {})
|
|
78
|
+
body = stage.context.get("body")
|
|
79
|
+
json_body = stage.context.get("json_body")
|
|
80
|
+
timeout = stage.context.get("timeout", 30)
|
|
81
|
+
verify_ssl = stage.context.get("verify_ssl", True)
|
|
82
|
+
expected_status = stage.context.get("expected_status")
|
|
83
|
+
|
|
84
|
+
if not url:
|
|
85
|
+
return TaskResult.terminal(error="No 'url' specified in context")
|
|
86
|
+
|
|
87
|
+
if method not in self.SUPPORTED_METHODS:
|
|
88
|
+
return TaskResult.terminal(error=f"Unsupported method '{method}'. Supported: {self.SUPPORTED_METHODS}")
|
|
89
|
+
|
|
90
|
+
# Handle JSON body
|
|
91
|
+
if json_body is not None:
|
|
92
|
+
body = json.dumps(json_body)
|
|
93
|
+
headers.setdefault("Content-Type", "application/json")
|
|
94
|
+
|
|
95
|
+
# Encode body if present
|
|
96
|
+
data = body.encode("utf-8") if body else None
|
|
97
|
+
|
|
98
|
+
# Build request
|
|
99
|
+
request = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
100
|
+
|
|
101
|
+
# SSL context
|
|
102
|
+
ssl_context = None
|
|
103
|
+
if not verify_ssl:
|
|
104
|
+
ssl_context = ssl.create_default_context()
|
|
105
|
+
ssl_context.check_hostname = False
|
|
106
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
107
|
+
|
|
108
|
+
print(f" [HTTPTask] {method} {url}")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
start_time = time.time()
|
|
112
|
+
with urllib.request.urlopen(request, timeout=timeout, context=ssl_context) as response:
|
|
113
|
+
elapsed_ms = int((time.time() - start_time) * 1000)
|
|
114
|
+
|
|
115
|
+
response_body = response.read().decode("utf-8")
|
|
116
|
+
response_headers = dict(response.headers)
|
|
117
|
+
status_code = response.status
|
|
118
|
+
final_url = response.url
|
|
119
|
+
|
|
120
|
+
except urllib.error.HTTPError as e:
|
|
121
|
+
elapsed_ms = int((time.time() - start_time) * 1000)
|
|
122
|
+
response_body = e.read().decode("utf-8") if e.fp else ""
|
|
123
|
+
response_headers = dict(e.headers) if e.headers else {}
|
|
124
|
+
status_code = e.code
|
|
125
|
+
final_url = url
|
|
126
|
+
|
|
127
|
+
except urllib.error.URLError as e:
|
|
128
|
+
return TaskResult.terminal(error=f"URL error: {e.reason}")
|
|
129
|
+
|
|
130
|
+
except TimeoutError:
|
|
131
|
+
return TaskResult.terminal(error=f"Request timed out after {timeout}s")
|
|
132
|
+
|
|
133
|
+
outputs = {
|
|
134
|
+
"status_code": status_code,
|
|
135
|
+
"headers": response_headers,
|
|
136
|
+
"body": response_body,
|
|
137
|
+
"elapsed_ms": elapsed_ms,
|
|
138
|
+
"url": final_url,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Check expected status
|
|
142
|
+
if expected_status is not None and status_code != expected_status:
|
|
143
|
+
print(f" [HTTPTask] Failed: expected {expected_status}, got {status_code}")
|
|
144
|
+
return TaskResult.terminal(
|
|
145
|
+
error=f"Expected status {expected_status}, got {status_code}",
|
|
146
|
+
context=outputs,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
print(f" [HTTPTask] {status_code} in {elapsed_ms}ms")
|
|
150
|
+
return TaskResult.success(outputs=outputs)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# =============================================================================
|
|
154
|
+
# Helper: Setup pipeline infrastructure
|
|
155
|
+
# =============================================================================
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def setup_pipeline_runner(store: WorkflowStore, queue: Queue) -> tuple[QueueProcessor, Orchestrator]:
|
|
159
|
+
"""Create processor and orchestrator with HTTPTask registered."""
|
|
160
|
+
task_registry = TaskRegistry()
|
|
161
|
+
task_registry.register("http", HTTPTask)
|
|
162
|
+
|
|
163
|
+
processor = QueueProcessor(queue)
|
|
164
|
+
|
|
165
|
+
handlers: list[Any] = [
|
|
166
|
+
StartWorkflowHandler(queue, store),
|
|
167
|
+
StartStageHandler(queue, store),
|
|
168
|
+
StartTaskHandler(queue, store),
|
|
169
|
+
RunTaskHandler(queue, store, task_registry),
|
|
170
|
+
CompleteTaskHandler(queue, store),
|
|
171
|
+
CompleteStageHandler(queue, store),
|
|
172
|
+
CompleteWorkflowHandler(queue, store),
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
for handler in handlers:
|
|
176
|
+
processor.register_handler(handler)
|
|
177
|
+
|
|
178
|
+
orchestrator = Orchestrator(queue)
|
|
179
|
+
return processor, orchestrator
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# =============================================================================
|
|
183
|
+
# Example 1: Simple GET Request
|
|
184
|
+
# =============================================================================
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def example_simple_get() -> None:
|
|
188
|
+
"""Make a simple GET request to a public API."""
|
|
189
|
+
print("\n" + "=" * 60)
|
|
190
|
+
print("Example 1: Simple GET Request")
|
|
191
|
+
print("=" * 60)
|
|
192
|
+
|
|
193
|
+
store = SqliteWorkflowStore("sqlite:///:memory:", create_tables=True)
|
|
194
|
+
queue = SqliteQueue("sqlite:///:memory:", table_name="queue_messages")
|
|
195
|
+
queue._create_table()
|
|
196
|
+
processor, orchestrator = setup_pipeline_runner(store, queue)
|
|
197
|
+
|
|
198
|
+
workflow = Workflow.create(
|
|
199
|
+
application="http-example",
|
|
200
|
+
name="Simple GET",
|
|
201
|
+
stages=[
|
|
202
|
+
StageExecution(
|
|
203
|
+
ref_id="1",
|
|
204
|
+
type="http",
|
|
205
|
+
name="Get IP Info",
|
|
206
|
+
context={
|
|
207
|
+
"url": "https://httpbin.org/ip",
|
|
208
|
+
"method": "GET",
|
|
209
|
+
},
|
|
210
|
+
tasks=[
|
|
211
|
+
TaskExecution.create(
|
|
212
|
+
name="HTTP GET",
|
|
213
|
+
implementing_class="http",
|
|
214
|
+
stage_start=True,
|
|
215
|
+
stage_end=True,
|
|
216
|
+
),
|
|
217
|
+
],
|
|
218
|
+
),
|
|
219
|
+
],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
store.store(workflow)
|
|
223
|
+
orchestrator.start(workflow)
|
|
224
|
+
processor.process_all(timeout=30.0)
|
|
225
|
+
|
|
226
|
+
result = store.retrieve(workflow.id)
|
|
227
|
+
print(f"\nWorkflow Status: {result.status}")
|
|
228
|
+
print(f"Response Status: {result.stages[0].outputs.get('status_code')}")
|
|
229
|
+
print(f"Response Body: {result.stages[0].outputs.get('body', '')[:200]}")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# =============================================================================
|
|
233
|
+
# Example 2: POST with JSON Body
|
|
234
|
+
# =============================================================================
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def example_post_json() -> None:
|
|
238
|
+
"""Make a POST request with JSON payload."""
|
|
239
|
+
print("\n" + "=" * 60)
|
|
240
|
+
print("Example 2: POST with JSON Body")
|
|
241
|
+
print("=" * 60)
|
|
242
|
+
|
|
243
|
+
store = SqliteWorkflowStore("sqlite:///:memory:", create_tables=True)
|
|
244
|
+
queue = SqliteQueue("sqlite:///:memory:", table_name="queue_messages")
|
|
245
|
+
queue._create_table()
|
|
246
|
+
processor, orchestrator = setup_pipeline_runner(store, queue)
|
|
247
|
+
|
|
248
|
+
workflow = Workflow.create(
|
|
249
|
+
application="http-example",
|
|
250
|
+
name="POST JSON",
|
|
251
|
+
stages=[
|
|
252
|
+
StageExecution(
|
|
253
|
+
ref_id="1",
|
|
254
|
+
type="http",
|
|
255
|
+
name="Create Resource",
|
|
256
|
+
context={
|
|
257
|
+
"url": "https://httpbin.org/post",
|
|
258
|
+
"method": "POST",
|
|
259
|
+
"json_body": {
|
|
260
|
+
"name": "Stabilize",
|
|
261
|
+
"version": "0.9.0",
|
|
262
|
+
"features": ["DAG", "parallel", "retry"],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
tasks=[
|
|
266
|
+
TaskExecution.create(
|
|
267
|
+
name="HTTP POST",
|
|
268
|
+
implementing_class="http",
|
|
269
|
+
stage_start=True,
|
|
270
|
+
stage_end=True,
|
|
271
|
+
),
|
|
272
|
+
],
|
|
273
|
+
),
|
|
274
|
+
],
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
store.store(workflow)
|
|
278
|
+
orchestrator.start(workflow)
|
|
279
|
+
processor.process_all(timeout=30.0)
|
|
280
|
+
|
|
281
|
+
result = store.retrieve(workflow.id)
|
|
282
|
+
print(f"\nWorkflow Status: {result.status}")
|
|
283
|
+
print(f"Response Status: {result.stages[0].outputs.get('status_code')}")
|
|
284
|
+
|
|
285
|
+
body = result.stages[0].outputs.get("body", "")
|
|
286
|
+
if body:
|
|
287
|
+
try:
|
|
288
|
+
data = json.loads(body)
|
|
289
|
+
print(f"Echoed JSON: {json.dumps(data.get('json', {}), indent=2)}")
|
|
290
|
+
except json.JSONDecodeError:
|
|
291
|
+
print(f"Response: {body[:200]}")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# =============================================================================
|
|
295
|
+
# Example 3: Sequential API Workflow
|
|
296
|
+
# =============================================================================
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def example_sequential_api() -> None:
|
|
300
|
+
"""Sequential API calls: GET -> POST -> GET to verify."""
|
|
301
|
+
print("\n" + "=" * 60)
|
|
302
|
+
print("Example 3: Sequential API Workflow")
|
|
303
|
+
print("=" * 60)
|
|
304
|
+
|
|
305
|
+
store = SqliteWorkflowStore("sqlite:///:memory:", create_tables=True)
|
|
306
|
+
queue = SqliteQueue("sqlite:///:memory:", table_name="queue_messages")
|
|
307
|
+
queue._create_table()
|
|
308
|
+
processor, orchestrator = setup_pipeline_runner(store, queue)
|
|
309
|
+
|
|
310
|
+
workflow = Workflow.create(
|
|
311
|
+
application="http-example",
|
|
312
|
+
name="Sequential API",
|
|
313
|
+
stages=[
|
|
314
|
+
StageExecution(
|
|
315
|
+
ref_id="1",
|
|
316
|
+
type="http",
|
|
317
|
+
name="Step 1: Get Headers",
|
|
318
|
+
context={
|
|
319
|
+
"url": "https://httpbin.org/headers",
|
|
320
|
+
"method": "GET",
|
|
321
|
+
"headers": {"X-Request-ID": "step-1"},
|
|
322
|
+
},
|
|
323
|
+
tasks=[
|
|
324
|
+
TaskExecution.create(
|
|
325
|
+
name="GET headers",
|
|
326
|
+
implementing_class="http",
|
|
327
|
+
stage_start=True,
|
|
328
|
+
stage_end=True,
|
|
329
|
+
),
|
|
330
|
+
],
|
|
331
|
+
),
|
|
332
|
+
StageExecution(
|
|
333
|
+
ref_id="2",
|
|
334
|
+
type="http",
|
|
335
|
+
name="Step 2: Post Data",
|
|
336
|
+
requisite_stage_ref_ids={"1"},
|
|
337
|
+
context={
|
|
338
|
+
"url": "https://httpbin.org/post",
|
|
339
|
+
"method": "POST",
|
|
340
|
+
"json_body": {"step": 2, "data": "from step 1"},
|
|
341
|
+
"headers": {"X-Request-ID": "step-2"},
|
|
342
|
+
},
|
|
343
|
+
tasks=[
|
|
344
|
+
TaskExecution.create(
|
|
345
|
+
name="POST data",
|
|
346
|
+
implementing_class="http",
|
|
347
|
+
stage_start=True,
|
|
348
|
+
stage_end=True,
|
|
349
|
+
),
|
|
350
|
+
],
|
|
351
|
+
),
|
|
352
|
+
StageExecution(
|
|
353
|
+
ref_id="3",
|
|
354
|
+
type="http",
|
|
355
|
+
name="Step 3: Verify",
|
|
356
|
+
requisite_stage_ref_ids={"2"},
|
|
357
|
+
context={
|
|
358
|
+
"url": "https://httpbin.org/get",
|
|
359
|
+
"method": "GET",
|
|
360
|
+
"headers": {"X-Request-ID": "step-3-verify"},
|
|
361
|
+
},
|
|
362
|
+
tasks=[
|
|
363
|
+
TaskExecution.create(
|
|
364
|
+
name="GET verify",
|
|
365
|
+
implementing_class="http",
|
|
366
|
+
stage_start=True,
|
|
367
|
+
stage_end=True,
|
|
368
|
+
),
|
|
369
|
+
],
|
|
370
|
+
),
|
|
371
|
+
],
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
store.store(workflow)
|
|
375
|
+
orchestrator.start(workflow)
|
|
376
|
+
processor.process_all(timeout=60.0)
|
|
377
|
+
|
|
378
|
+
result = store.retrieve(workflow.id)
|
|
379
|
+
print(f"\nWorkflow Status: {result.status}")
|
|
380
|
+
for stage in result.stages:
|
|
381
|
+
status = stage.outputs.get("status_code", "N/A")
|
|
382
|
+
elapsed = stage.outputs.get("elapsed_ms", "N/A")
|
|
383
|
+
print(f" {stage.name}: {status} ({elapsed}ms)")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# =============================================================================
|
|
387
|
+
# Example 4: Parallel Requests
|
|
388
|
+
# =============================================================================
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def example_parallel_requests() -> None:
|
|
392
|
+
"""Make parallel requests to multiple endpoints."""
|
|
393
|
+
print("\n" + "=" * 60)
|
|
394
|
+
print("Example 4: Parallel Requests")
|
|
395
|
+
print("=" * 60)
|
|
396
|
+
|
|
397
|
+
store = SqliteWorkflowStore("sqlite:///:memory:", create_tables=True)
|
|
398
|
+
queue = SqliteQueue("sqlite:///:memory:", table_name="queue_messages")
|
|
399
|
+
queue._create_table()
|
|
400
|
+
processor, orchestrator = setup_pipeline_runner(store, queue)
|
|
401
|
+
|
|
402
|
+
# Start
|
|
403
|
+
# / | \
|
|
404
|
+
# EP1 EP2 EP3
|
|
405
|
+
# \ | /
|
|
406
|
+
# Collect
|
|
407
|
+
|
|
408
|
+
workflow = Workflow.create(
|
|
409
|
+
application="http-example",
|
|
410
|
+
name="Parallel Requests",
|
|
411
|
+
stages=[
|
|
412
|
+
StageExecution(
|
|
413
|
+
ref_id="start",
|
|
414
|
+
type="http",
|
|
415
|
+
name="Start",
|
|
416
|
+
context={
|
|
417
|
+
"url": "https://httpbin.org/get?stage=start",
|
|
418
|
+
"method": "GET",
|
|
419
|
+
},
|
|
420
|
+
tasks=[
|
|
421
|
+
TaskExecution.create(
|
|
422
|
+
name="Start request",
|
|
423
|
+
implementing_class="http",
|
|
424
|
+
stage_start=True,
|
|
425
|
+
stage_end=True,
|
|
426
|
+
),
|
|
427
|
+
],
|
|
428
|
+
),
|
|
429
|
+
# Parallel branches
|
|
430
|
+
StageExecution(
|
|
431
|
+
ref_id="ep1",
|
|
432
|
+
type="http",
|
|
433
|
+
name="Endpoint 1 (IP)",
|
|
434
|
+
requisite_stage_ref_ids={"start"},
|
|
435
|
+
context={
|
|
436
|
+
"url": "https://httpbin.org/ip",
|
|
437
|
+
"method": "GET",
|
|
438
|
+
},
|
|
439
|
+
tasks=[
|
|
440
|
+
TaskExecution.create(
|
|
441
|
+
name="Get IP",
|
|
442
|
+
implementing_class="http",
|
|
443
|
+
stage_start=True,
|
|
444
|
+
stage_end=True,
|
|
445
|
+
),
|
|
446
|
+
],
|
|
447
|
+
),
|
|
448
|
+
StageExecution(
|
|
449
|
+
ref_id="ep2",
|
|
450
|
+
type="http",
|
|
451
|
+
name="Endpoint 2 (Headers)",
|
|
452
|
+
requisite_stage_ref_ids={"start"},
|
|
453
|
+
context={
|
|
454
|
+
"url": "https://httpbin.org/headers",
|
|
455
|
+
"method": "GET",
|
|
456
|
+
},
|
|
457
|
+
tasks=[
|
|
458
|
+
TaskExecution.create(
|
|
459
|
+
name="Get Headers",
|
|
460
|
+
implementing_class="http",
|
|
461
|
+
stage_start=True,
|
|
462
|
+
stage_end=True,
|
|
463
|
+
),
|
|
464
|
+
],
|
|
465
|
+
),
|
|
466
|
+
StageExecution(
|
|
467
|
+
ref_id="ep3",
|
|
468
|
+
type="http",
|
|
469
|
+
name="Endpoint 3 (User-Agent)",
|
|
470
|
+
requisite_stage_ref_ids={"start"},
|
|
471
|
+
context={
|
|
472
|
+
"url": "https://httpbin.org/user-agent",
|
|
473
|
+
"method": "GET",
|
|
474
|
+
},
|
|
475
|
+
tasks=[
|
|
476
|
+
TaskExecution.create(
|
|
477
|
+
name="Get User-Agent",
|
|
478
|
+
implementing_class="http",
|
|
479
|
+
stage_start=True,
|
|
480
|
+
stage_end=True,
|
|
481
|
+
),
|
|
482
|
+
],
|
|
483
|
+
),
|
|
484
|
+
# Join
|
|
485
|
+
StageExecution(
|
|
486
|
+
ref_id="collect",
|
|
487
|
+
type="http",
|
|
488
|
+
name="Collect Results",
|
|
489
|
+
requisite_stage_ref_ids={"ep1", "ep2", "ep3"},
|
|
490
|
+
context={
|
|
491
|
+
"url": "https://httpbin.org/get?stage=complete",
|
|
492
|
+
"method": "GET",
|
|
493
|
+
},
|
|
494
|
+
tasks=[
|
|
495
|
+
TaskExecution.create(
|
|
496
|
+
name="Final request",
|
|
497
|
+
implementing_class="http",
|
|
498
|
+
stage_start=True,
|
|
499
|
+
stage_end=True,
|
|
500
|
+
),
|
|
501
|
+
],
|
|
502
|
+
),
|
|
503
|
+
],
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
store.store(workflow)
|
|
507
|
+
orchestrator.start(workflow)
|
|
508
|
+
processor.process_all(timeout=60.0)
|
|
509
|
+
|
|
510
|
+
result = store.retrieve(workflow.id)
|
|
511
|
+
print(f"\nWorkflow Status: {result.status}")
|
|
512
|
+
for stage in result.stages:
|
|
513
|
+
status = stage.outputs.get("status_code", "N/A")
|
|
514
|
+
elapsed = stage.outputs.get("elapsed_ms", "N/A")
|
|
515
|
+
print(f" {stage.name}: {status} ({elapsed}ms)")
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
# =============================================================================
|
|
519
|
+
# Example 5: All HTTP Methods
|
|
520
|
+
# =============================================================================
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def example_all_methods() -> None:
|
|
524
|
+
"""Demonstrate all supported HTTP methods."""
|
|
525
|
+
print("\n" + "=" * 60)
|
|
526
|
+
print("Example 5: All HTTP Methods")
|
|
527
|
+
print("=" * 60)
|
|
528
|
+
|
|
529
|
+
store = SqliteWorkflowStore("sqlite:///:memory:", create_tables=True)
|
|
530
|
+
queue = SqliteQueue("sqlite:///:memory:", table_name="queue_messages")
|
|
531
|
+
queue._create_table()
|
|
532
|
+
processor, orchestrator = setup_pipeline_runner(store, queue)
|
|
533
|
+
|
|
534
|
+
methods = [
|
|
535
|
+
("GET", "https://httpbin.org/get", None),
|
|
536
|
+
("POST", "https://httpbin.org/post", {"action": "create"}),
|
|
537
|
+
("PUT", "https://httpbin.org/put", {"action": "update"}),
|
|
538
|
+
("PATCH", "https://httpbin.org/patch", {"action": "partial"}),
|
|
539
|
+
("DELETE", "https://httpbin.org/delete", None),
|
|
540
|
+
("HEAD", "https://httpbin.org/get", None),
|
|
541
|
+
("OPTIONS", "https://httpbin.org/get", None),
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
stages = []
|
|
545
|
+
prev_ref = None
|
|
546
|
+
for i, (method, url, body) in enumerate(methods, 1):
|
|
547
|
+
ref_id = str(i)
|
|
548
|
+
context: dict[str, Any] = {"url": url, "method": method}
|
|
549
|
+
if body:
|
|
550
|
+
context["json_body"] = body
|
|
551
|
+
|
|
552
|
+
stage = StageExecution(
|
|
553
|
+
ref_id=ref_id,
|
|
554
|
+
type="http",
|
|
555
|
+
name=f"{method} Request",
|
|
556
|
+
requisite_stage_ref_ids={prev_ref} if prev_ref else set(),
|
|
557
|
+
context=context,
|
|
558
|
+
tasks=[
|
|
559
|
+
TaskExecution.create(
|
|
560
|
+
name=f"HTTP {method}",
|
|
561
|
+
implementing_class="http",
|
|
562
|
+
stage_start=True,
|
|
563
|
+
stage_end=True,
|
|
564
|
+
),
|
|
565
|
+
],
|
|
566
|
+
)
|
|
567
|
+
stages.append(stage)
|
|
568
|
+
prev_ref = ref_id
|
|
569
|
+
|
|
570
|
+
workflow = Workflow.create(
|
|
571
|
+
application="http-example",
|
|
572
|
+
name="All HTTP Methods",
|
|
573
|
+
stages=stages,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
store.store(workflow)
|
|
577
|
+
orchestrator.start(workflow)
|
|
578
|
+
processor.process_all(timeout=120.0)
|
|
579
|
+
|
|
580
|
+
result = store.retrieve(workflow.id)
|
|
581
|
+
print(f"\nWorkflow Status: {result.status}")
|
|
582
|
+
for stage in result.stages:
|
|
583
|
+
status = stage.outputs.get("status_code", "N/A")
|
|
584
|
+
elapsed = stage.outputs.get("elapsed_ms", "N/A")
|
|
585
|
+
print(f" {stage.name}: {status} ({elapsed}ms)")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# =============================================================================
|
|
589
|
+
# Main
|
|
590
|
+
# =============================================================================
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
if __name__ == "__main__":
|
|
594
|
+
print("Stabilize HTTP Examples")
|
|
595
|
+
print("=" * 60)
|
|
596
|
+
print("Using httpbin.org for testing")
|
|
597
|
+
|
|
598
|
+
example_simple_get()
|
|
599
|
+
example_post_json()
|
|
600
|
+
example_sequential_api()
|
|
601
|
+
example_parallel_requests()
|
|
602
|
+
example_all_methods()
|
|
603
|
+
|
|
604
|
+
print("\n" + "=" * 60)
|
|
605
|
+
print("All examples completed!")
|
|
606
|
+
print("=" * 60)
|