horsies 0.1.0a3__py3-none-any.whl → 0.1.0a5__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.
- horsies/core/app.py +67 -47
- horsies/core/banner.py +27 -27
- horsies/core/brokers/postgres.py +315 -288
- horsies/core/cli.py +7 -2
- horsies/core/errors.py +3 -0
- horsies/core/models/app.py +87 -64
- horsies/core/models/recovery.py +30 -21
- horsies/core/models/schedule.py +30 -19
- horsies/core/models/tasks.py +1 -0
- horsies/core/models/workflow.py +489 -202
- horsies/core/models/workflow_pg.py +3 -1
- horsies/core/scheduler/service.py +5 -1
- horsies/core/scheduler/state.py +39 -27
- horsies/core/task_decorator.py +138 -0
- horsies/core/types/status.py +14 -12
- horsies/core/utils/imports.py +10 -10
- horsies/core/worker/worker.py +197 -139
- horsies/core/workflows/engine.py +487 -352
- horsies/core/workflows/recovery.py +148 -119
- {horsies-0.1.0a3.dist-info → horsies-0.1.0a5.dist-info}/METADATA +1 -1
- horsies-0.1.0a5.dist-info/RECORD +42 -0
- horsies-0.1.0a3.dist-info/RECORD +0 -42
- {horsies-0.1.0a3.dist-info → horsies-0.1.0a5.dist-info}/WHEEL +0 -0
- {horsies-0.1.0a3.dist-info → horsies-0.1.0a5.dist-info}/entry_points.txt +0 -0
- {horsies-0.1.0a3.dist-info → horsies-0.1.0a5.dist-info}/top_level.txt +0 -0
|
@@ -29,6 +29,120 @@ logger = get_logger('workflow.recovery')
|
|
|
29
29
|
_WF_TASK_TERMINAL_VALUES: list[str] = [s.value for s in WORKFLOW_TASK_TERMINAL_STATES]
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
GET_PENDING_WITH_TERMINAL_DEPS_SQL = text("""
|
|
33
|
+
SELECT wt.workflow_id, wt.task_index, wt.dependencies, wt.allow_failed_deps
|
|
34
|
+
FROM horsies_workflow_tasks wt
|
|
35
|
+
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
36
|
+
WHERE wt.status = 'PENDING'
|
|
37
|
+
AND w.status = 'RUNNING'
|
|
38
|
+
AND NOT EXISTS (
|
|
39
|
+
SELECT 1 FROM horsies_workflow_tasks dep
|
|
40
|
+
WHERE dep.workflow_id = wt.workflow_id
|
|
41
|
+
AND dep.task_index = ANY(wt.dependencies)
|
|
42
|
+
AND NOT (dep.status = ANY(:wf_task_terminal_states))
|
|
43
|
+
)
|
|
44
|
+
""")
|
|
45
|
+
|
|
46
|
+
COUNT_FAILED_DEPS_SQL = text("""
|
|
47
|
+
SELECT COUNT(*) FROM horsies_workflow_tasks
|
|
48
|
+
WHERE workflow_id = :wf_id
|
|
49
|
+
AND task_index = ANY(:deps)
|
|
50
|
+
AND status IN ('FAILED', 'SKIPPED')
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
SKIP_PENDING_TASK_SQL = text("""
|
|
54
|
+
UPDATE horsies_workflow_tasks
|
|
55
|
+
SET status = 'SKIPPED'
|
|
56
|
+
WHERE workflow_id = :wf_id AND task_index = :idx AND status = 'PENDING'
|
|
57
|
+
""")
|
|
58
|
+
|
|
59
|
+
MARK_PENDING_READY_SQL = text("""
|
|
60
|
+
UPDATE horsies_workflow_tasks
|
|
61
|
+
SET status = 'READY'
|
|
62
|
+
WHERE workflow_id = :wf_id AND task_index = :idx AND status = 'PENDING'
|
|
63
|
+
""")
|
|
64
|
+
|
|
65
|
+
GET_READY_NOT_ENQUEUED_SQL = text("""
|
|
66
|
+
SELECT wt.workflow_id, wt.task_index, wt.dependencies
|
|
67
|
+
FROM horsies_workflow_tasks wt
|
|
68
|
+
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
69
|
+
WHERE wt.status = 'READY'
|
|
70
|
+
AND wt.task_id IS NULL
|
|
71
|
+
AND wt.is_subworkflow = FALSE
|
|
72
|
+
AND w.status = 'RUNNING'
|
|
73
|
+
""")
|
|
74
|
+
|
|
75
|
+
GET_READY_SUBWORKFLOWS_NOT_STARTED_SQL = text("""
|
|
76
|
+
SELECT wt.workflow_id, wt.task_index, wt.dependencies, w.depth, w.root_workflow_id
|
|
77
|
+
FROM horsies_workflow_tasks wt
|
|
78
|
+
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
79
|
+
WHERE wt.status = 'READY'
|
|
80
|
+
AND wt.is_subworkflow = TRUE
|
|
81
|
+
AND wt.sub_workflow_id IS NULL
|
|
82
|
+
AND w.status = 'RUNNING'
|
|
83
|
+
""")
|
|
84
|
+
|
|
85
|
+
RESET_SUBWORKFLOW_TO_PENDING_SQL = text("""
|
|
86
|
+
UPDATE horsies_workflow_tasks
|
|
87
|
+
SET status = 'PENDING'
|
|
88
|
+
WHERE workflow_id = :wf_id AND task_index = :idx AND status = 'READY'
|
|
89
|
+
""")
|
|
90
|
+
|
|
91
|
+
GET_COMPLETED_CHILDREN_NOT_UPDATED_SQL = text("""
|
|
92
|
+
SELECT child.id, child.parent_workflow_id, child.parent_task_index, child.status
|
|
93
|
+
FROM horsies_workflows child
|
|
94
|
+
JOIN horsies_workflows parent ON parent.id = child.parent_workflow_id
|
|
95
|
+
JOIN horsies_workflow_tasks wt ON wt.workflow_id = parent.id AND wt.task_index = child.parent_task_index
|
|
96
|
+
WHERE child.status IN ('COMPLETED', 'FAILED')
|
|
97
|
+
AND wt.status = 'RUNNING'
|
|
98
|
+
AND parent.status = 'RUNNING'
|
|
99
|
+
""")
|
|
100
|
+
|
|
101
|
+
GET_CRASHED_WORKER_TASKS_SQL = text("""
|
|
102
|
+
SELECT wt.workflow_id, wt.task_index, wt.task_id,
|
|
103
|
+
UPPER(t.status) as task_status, t.result as task_result
|
|
104
|
+
FROM horsies_workflow_tasks wt
|
|
105
|
+
JOIN horsies_tasks t ON t.id = wt.task_id
|
|
106
|
+
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
107
|
+
WHERE NOT (wt.status = ANY(:wf_task_terminal_states))
|
|
108
|
+
AND wt.task_id IS NOT NULL
|
|
109
|
+
AND wt.is_subworkflow = FALSE
|
|
110
|
+
AND w.status = 'RUNNING'
|
|
111
|
+
AND UPPER(t.status) IN ('COMPLETED', 'FAILED', 'CANCELLED')
|
|
112
|
+
""")
|
|
113
|
+
|
|
114
|
+
GET_TERMINAL_WORKFLOW_CANDIDATES_SQL = text("""
|
|
115
|
+
SELECT w.id, w.error, w.success_policy,
|
|
116
|
+
COUNT(*) FILTER (WHERE wt.status = 'FAILED') as failed_count
|
|
117
|
+
FROM horsies_workflows w
|
|
118
|
+
LEFT JOIN horsies_workflow_tasks wt ON wt.workflow_id = w.id
|
|
119
|
+
WHERE w.status = 'RUNNING'
|
|
120
|
+
AND NOT EXISTS (
|
|
121
|
+
SELECT 1 FROM horsies_workflow_tasks wt2
|
|
122
|
+
WHERE wt2.workflow_id = w.id
|
|
123
|
+
AND NOT (wt2.status = ANY(:wf_task_terminal_states))
|
|
124
|
+
)
|
|
125
|
+
GROUP BY w.id, w.error, w.success_policy
|
|
126
|
+
""")
|
|
127
|
+
|
|
128
|
+
MARK_WORKFLOW_COMPLETED_SQL = text("""
|
|
129
|
+
UPDATE horsies_workflows
|
|
130
|
+
SET status = 'COMPLETED', result = :result, completed_at = NOW(), updated_at = NOW()
|
|
131
|
+
WHERE id = :wf_id AND status = 'RUNNING'
|
|
132
|
+
""")
|
|
133
|
+
|
|
134
|
+
MARK_WORKFLOW_FAILED_SQL = text("""
|
|
135
|
+
UPDATE horsies_workflows
|
|
136
|
+
SET status = 'FAILED', result = :result, error = :error,
|
|
137
|
+
completed_at = NOW(), updated_at = NOW()
|
|
138
|
+
WHERE id = :wf_id AND status = 'RUNNING'
|
|
139
|
+
""")
|
|
140
|
+
|
|
141
|
+
NOTIFY_WORKFLOW_DONE_SQL = text("""
|
|
142
|
+
SELECT pg_notify('workflow_done', :wf_id)
|
|
143
|
+
""")
|
|
144
|
+
|
|
145
|
+
|
|
32
146
|
async def recover_stuck_workflows(
|
|
33
147
|
session: 'AsyncSession',
|
|
34
148
|
broker: 'PostgresBroker | None' = None,
|
|
@@ -56,19 +170,7 @@ async def recover_stuck_workflows(
|
|
|
56
170
|
# This happens when multiple dependencies complete concurrently and the PENDING→READY
|
|
57
171
|
# transition is missed due to timing
|
|
58
172
|
pending_ready = await session.execute(
|
|
59
|
-
|
|
60
|
-
SELECT wt.workflow_id, wt.task_index, wt.dependencies, wt.allow_failed_deps
|
|
61
|
-
FROM horsies_workflow_tasks wt
|
|
62
|
-
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
63
|
-
WHERE wt.status = 'PENDING'
|
|
64
|
-
AND w.status = 'RUNNING'
|
|
65
|
-
AND NOT EXISTS (
|
|
66
|
-
SELECT 1 FROM horsies_workflow_tasks dep
|
|
67
|
-
WHERE dep.workflow_id = wt.workflow_id
|
|
68
|
-
AND dep.task_index = ANY(wt.dependencies)
|
|
69
|
-
AND NOT (dep.status = ANY(:wf_task_terminal_states))
|
|
70
|
-
)
|
|
71
|
-
"""),
|
|
173
|
+
GET_PENDING_WITH_TERMINAL_DEPS_SQL,
|
|
72
174
|
{'wf_task_terminal_states': _WF_TASK_TERMINAL_VALUES},
|
|
73
175
|
)
|
|
74
176
|
|
|
@@ -83,12 +185,7 @@ async def recover_stuck_workflows(
|
|
|
83
185
|
|
|
84
186
|
# Check if any dependency failed/skipped
|
|
85
187
|
failed_check = await session.execute(
|
|
86
|
-
|
|
87
|
-
SELECT COUNT(*) FROM horsies_workflow_tasks
|
|
88
|
-
WHERE workflow_id = :wf_id
|
|
89
|
-
AND task_index = ANY(:deps)
|
|
90
|
-
AND status IN ('FAILED', 'SKIPPED')
|
|
91
|
-
"""),
|
|
188
|
+
COUNT_FAILED_DEPS_SQL,
|
|
92
189
|
{'wf_id': workflow_id, 'deps': dependencies},
|
|
93
190
|
)
|
|
94
191
|
failed_count = failed_check.scalar() or 0
|
|
@@ -96,11 +193,7 @@ async def recover_stuck_workflows(
|
|
|
96
193
|
if failed_count > 0 and not allow_failed_deps:
|
|
97
194
|
# Skip this task (propagate failure)
|
|
98
195
|
await session.execute(
|
|
99
|
-
|
|
100
|
-
UPDATE horsies_workflow_tasks
|
|
101
|
-
SET status = 'SKIPPED'
|
|
102
|
-
WHERE workflow_id = :wf_id AND task_index = :idx AND status = 'PENDING'
|
|
103
|
-
"""),
|
|
196
|
+
SKIP_PENDING_TASK_SQL,
|
|
104
197
|
{'wf_id': workflow_id, 'idx': task_index},
|
|
105
198
|
)
|
|
106
199
|
logger.info(
|
|
@@ -111,11 +204,7 @@ async def recover_stuck_workflows(
|
|
|
111
204
|
else:
|
|
112
205
|
# Mark READY and enqueue
|
|
113
206
|
await session.execute(
|
|
114
|
-
|
|
115
|
-
UPDATE horsies_workflow_tasks
|
|
116
|
-
SET status = 'READY'
|
|
117
|
-
WHERE workflow_id = :wf_id AND task_index = :idx AND status = 'PENDING'
|
|
118
|
-
"""),
|
|
207
|
+
MARK_PENDING_READY_SQL,
|
|
119
208
|
{'wf_id': workflow_id, 'idx': task_index},
|
|
120
209
|
)
|
|
121
210
|
dep_results: dict[
|
|
@@ -137,17 +226,7 @@ async def recover_stuck_workflows(
|
|
|
137
226
|
# Case 1: READY tasks not enqueued (task_id is NULL but status is READY)
|
|
138
227
|
# This happens if worker crashed after marking READY but before creating task
|
|
139
228
|
# Excludes SubWorkflowNodes (handled separately)
|
|
140
|
-
ready_not_enqueued = await session.execute(
|
|
141
|
-
text("""
|
|
142
|
-
SELECT wt.workflow_id, wt.task_index, wt.dependencies
|
|
143
|
-
FROM horsies_workflow_tasks wt
|
|
144
|
-
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
145
|
-
WHERE wt.status = 'READY'
|
|
146
|
-
AND wt.task_id IS NULL
|
|
147
|
-
AND wt.is_subworkflow = FALSE
|
|
148
|
-
AND w.status = 'RUNNING'
|
|
149
|
-
""")
|
|
150
|
-
)
|
|
229
|
+
ready_not_enqueued = await session.execute(GET_READY_NOT_ENQUEUED_SQL)
|
|
151
230
|
|
|
152
231
|
for row in ready_not_enqueued.fetchall():
|
|
153
232
|
workflow_id = row[0]
|
|
@@ -177,17 +256,7 @@ async def recover_stuck_workflows(
|
|
|
177
256
|
# Case 1.5: READY SubWorkflowNodes not started (sub_workflow_id is NULL)
|
|
178
257
|
# This happens if worker crashed after marking READY but before starting child workflow
|
|
179
258
|
# NOTE: This requires broker to start the child workflow, so we just mark them for retry
|
|
180
|
-
ready_subworkflows = await session.execute(
|
|
181
|
-
text("""
|
|
182
|
-
SELECT wt.workflow_id, wt.task_index, wt.dependencies, w.depth, w.root_workflow_id
|
|
183
|
-
FROM horsies_workflow_tasks wt
|
|
184
|
-
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
185
|
-
WHERE wt.status = 'READY'
|
|
186
|
-
AND wt.is_subworkflow = TRUE
|
|
187
|
-
AND wt.sub_workflow_id IS NULL
|
|
188
|
-
AND w.status = 'RUNNING'
|
|
189
|
-
""")
|
|
190
|
-
)
|
|
259
|
+
ready_subworkflows = await session.execute(GET_READY_SUBWORKFLOWS_NOT_STARTED_SQL)
|
|
191
260
|
|
|
192
261
|
for row in ready_subworkflows.fetchall():
|
|
193
262
|
workflow_id = row[0]
|
|
@@ -218,11 +287,7 @@ async def recover_stuck_workflows(
|
|
|
218
287
|
else:
|
|
219
288
|
# Reset to PENDING so a future evaluation can start it
|
|
220
289
|
await session.execute(
|
|
221
|
-
|
|
222
|
-
UPDATE horsies_workflow_tasks
|
|
223
|
-
SET status = 'PENDING'
|
|
224
|
-
WHERE workflow_id = :wf_id AND task_index = :idx AND status = 'READY'
|
|
225
|
-
"""),
|
|
290
|
+
RESET_SUBWORKFLOW_TO_PENDING_SQL,
|
|
226
291
|
{'wf_id': workflow_id, 'idx': task_index},
|
|
227
292
|
)
|
|
228
293
|
logger.info(
|
|
@@ -233,17 +298,7 @@ async def recover_stuck_workflows(
|
|
|
233
298
|
|
|
234
299
|
# Case 1.6: Child workflows completed but parent node not updated
|
|
235
300
|
# This happens if the _on_subworkflow_complete callback failed or was interrupted
|
|
236
|
-
completed_children = await session.execute(
|
|
237
|
-
text("""
|
|
238
|
-
SELECT child.id, child.parent_workflow_id, child.parent_task_index, child.status
|
|
239
|
-
FROM horsies_workflows child
|
|
240
|
-
JOIN horsies_workflows parent ON parent.id = child.parent_workflow_id
|
|
241
|
-
JOIN horsies_workflow_tasks wt ON wt.workflow_id = parent.id AND wt.task_index = child.parent_task_index
|
|
242
|
-
WHERE child.status IN ('COMPLETED', 'FAILED')
|
|
243
|
-
AND wt.status = 'RUNNING'
|
|
244
|
-
AND parent.status = 'RUNNING'
|
|
245
|
-
""")
|
|
246
|
-
)
|
|
301
|
+
completed_children = await session.execute(GET_COMPLETED_CHILDREN_NOT_UPDATED_SQL)
|
|
247
302
|
|
|
248
303
|
for row in completed_children.fetchall():
|
|
249
304
|
child_id = row[0]
|
|
@@ -267,18 +322,7 @@ async def recover_stuck_workflows(
|
|
|
267
322
|
# - on_workflow_task_complete() was never called (worker died)
|
|
268
323
|
# - workflow_tasks row stays RUNNING/ENQUEUED indefinitely
|
|
269
324
|
crashed_worker_tasks = await session.execute(
|
|
270
|
-
|
|
271
|
-
SELECT wt.workflow_id, wt.task_index, wt.task_id,
|
|
272
|
-
UPPER(t.status) as task_status, t.result as task_result
|
|
273
|
-
FROM horsies_workflow_tasks wt
|
|
274
|
-
JOIN horsies_tasks t ON t.id = wt.task_id
|
|
275
|
-
JOIN horsies_workflows w ON w.id = wt.workflow_id
|
|
276
|
-
WHERE NOT (wt.status = ANY(:wf_task_terminal_states))
|
|
277
|
-
AND wt.task_id IS NOT NULL
|
|
278
|
-
AND wt.is_subworkflow = FALSE
|
|
279
|
-
AND w.status = 'RUNNING'
|
|
280
|
-
AND UPPER(t.status) IN ('COMPLETED', 'FAILED', 'CANCELLED')
|
|
281
|
-
"""),
|
|
325
|
+
GET_CRASHED_WORKER_TASKS_SQL,
|
|
282
326
|
{'wf_task_terminal_states': _WF_TASK_TERMINAL_VALUES},
|
|
283
327
|
)
|
|
284
328
|
|
|
@@ -338,19 +382,7 @@ async def recover_stuck_workflows(
|
|
|
338
382
|
# This handles both completed and failed workflows, respecting success_policy.
|
|
339
383
|
# This happens if worker crashed after completing last task but before updating workflow
|
|
340
384
|
terminal_candidates = await session.execute(
|
|
341
|
-
|
|
342
|
-
SELECT w.id, w.error, w.success_policy,
|
|
343
|
-
COUNT(*) FILTER (WHERE wt.status = 'FAILED') as failed_count
|
|
344
|
-
FROM horsies_workflows w
|
|
345
|
-
LEFT JOIN horsies_workflow_tasks wt ON wt.workflow_id = w.id
|
|
346
|
-
WHERE w.status = 'RUNNING'
|
|
347
|
-
AND NOT EXISTS (
|
|
348
|
-
SELECT 1 FROM horsies_workflow_tasks wt2
|
|
349
|
-
WHERE wt2.workflow_id = w.id
|
|
350
|
-
AND NOT (wt2.status = ANY(:wf_task_terminal_states))
|
|
351
|
-
)
|
|
352
|
-
GROUP BY w.id, w.error, w.success_policy
|
|
353
|
-
"""),
|
|
385
|
+
GET_TERMINAL_WORKFLOW_CANDIDATES_SQL,
|
|
354
386
|
{'wf_task_terminal_states': _WF_TASK_TERMINAL_VALUES},
|
|
355
387
|
)
|
|
356
388
|
|
|
@@ -377,11 +409,7 @@ async def recover_stuck_workflows(
|
|
|
377
409
|
|
|
378
410
|
if workflow_succeeded:
|
|
379
411
|
await session.execute(
|
|
380
|
-
|
|
381
|
-
UPDATE horsies_workflows
|
|
382
|
-
SET status = 'COMPLETED', result = :result, completed_at = NOW(), updated_at = NOW()
|
|
383
|
-
WHERE id = :wf_id AND status = 'RUNNING'
|
|
384
|
-
"""),
|
|
412
|
+
MARK_WORKFLOW_COMPLETED_SQL,
|
|
385
413
|
{'wf_id': workflow_id, 'result': final_result},
|
|
386
414
|
)
|
|
387
415
|
logger.info(f'Recovered stuck COMPLETED workflow: {workflow_id}')
|
|
@@ -394,19 +422,14 @@ async def recover_stuck_workflows(
|
|
|
394
422
|
)
|
|
395
423
|
|
|
396
424
|
await session.execute(
|
|
397
|
-
|
|
398
|
-
UPDATE horsies_workflows
|
|
399
|
-
SET status = 'FAILED', result = :result, error = :error,
|
|
400
|
-
completed_at = NOW(), updated_at = NOW()
|
|
401
|
-
WHERE id = :wf_id AND status = 'RUNNING'
|
|
402
|
-
"""),
|
|
425
|
+
MARK_WORKFLOW_FAILED_SQL,
|
|
403
426
|
{'wf_id': workflow_id, 'result': final_result, 'error': error_payload},
|
|
404
427
|
)
|
|
405
428
|
logger.info(f'Recovered stuck FAILED workflow: {workflow_id}')
|
|
406
429
|
|
|
407
430
|
# Send NOTIFY for workflow completion
|
|
408
431
|
await session.execute(
|
|
409
|
-
|
|
432
|
+
NOTIFY_WORKFLOW_DONE_SQL,
|
|
410
433
|
{'wf_id': workflow_id},
|
|
411
434
|
)
|
|
412
435
|
recovered += 1
|
|
@@ -414,6 +437,16 @@ async def recover_stuck_workflows(
|
|
|
414
437
|
return recovered
|
|
415
438
|
|
|
416
439
|
|
|
440
|
+
GET_FIRST_FAILED_TASK_RESULT_SQL = text("""
|
|
441
|
+
SELECT result
|
|
442
|
+
FROM horsies_workflow_tasks
|
|
443
|
+
WHERE workflow_id = :wf_id
|
|
444
|
+
AND status = 'FAILED'
|
|
445
|
+
ORDER BY task_index ASC
|
|
446
|
+
LIMIT 1
|
|
447
|
+
""")
|
|
448
|
+
|
|
449
|
+
|
|
417
450
|
async def _get_first_failed_task_error(
|
|
418
451
|
session: 'AsyncSession',
|
|
419
452
|
workflow_id: str,
|
|
@@ -427,14 +460,7 @@ async def _get_first_failed_task_error(
|
|
|
427
460
|
from horsies.core.codec.serde import dumps_json
|
|
428
461
|
|
|
429
462
|
result = await session.execute(
|
|
430
|
-
|
|
431
|
-
SELECT result
|
|
432
|
-
FROM horsies_workflow_tasks
|
|
433
|
-
WHERE workflow_id = :wf_id
|
|
434
|
-
AND status = 'FAILED'
|
|
435
|
-
ORDER BY task_index ASC
|
|
436
|
-
LIMIT 1
|
|
437
|
-
"""),
|
|
463
|
+
GET_FIRST_FAILED_TASK_RESULT_SQL,
|
|
438
464
|
{'wf_id': workflow_id},
|
|
439
465
|
)
|
|
440
466
|
|
|
@@ -449,6 +475,15 @@ async def _get_first_failed_task_error(
|
|
|
449
475
|
return None
|
|
450
476
|
|
|
451
477
|
|
|
478
|
+
GET_DEPENDENCY_RESULTS_SQL = text("""
|
|
479
|
+
SELECT task_index, status, result
|
|
480
|
+
FROM horsies_workflow_tasks
|
|
481
|
+
WHERE workflow_id = :wf_id
|
|
482
|
+
AND task_index = ANY(:indices)
|
|
483
|
+
AND status = ANY(:wf_task_terminal_states)
|
|
484
|
+
""")
|
|
485
|
+
|
|
486
|
+
|
|
452
487
|
async def _get_dependency_results(
|
|
453
488
|
session: 'AsyncSession',
|
|
454
489
|
workflow_id: str,
|
|
@@ -466,13 +501,7 @@ async def _get_dependency_results(
|
|
|
466
501
|
return {}
|
|
467
502
|
|
|
468
503
|
result = await session.execute(
|
|
469
|
-
|
|
470
|
-
SELECT task_index, status, result
|
|
471
|
-
FROM horsies_workflow_tasks
|
|
472
|
-
WHERE workflow_id = :wf_id
|
|
473
|
-
AND task_index = ANY(:indices)
|
|
474
|
-
AND status = ANY(:wf_task_terminal_states)
|
|
475
|
-
"""),
|
|
504
|
+
GET_DEPENDENCY_RESULTS_SQL,
|
|
476
505
|
{
|
|
477
506
|
'wf_id': workflow_id,
|
|
478
507
|
'indices': dependency_indices,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
horsies/__init__.py,sha256=rLxes5IuyzggohtXKRsHoddeyqEy0LsgwDlk09itKXk,2769
|
|
2
|
+
horsies/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
horsies/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
horsies/core/app.py,sha256=5HkGil3sZvJAZAerRuPulKUJO-U-vgtrmQxtrVGKc2w,22218
|
|
5
|
+
horsies/core/banner.py,sha256=lgdM5x9aa7kfD0a91wLCSOh61zXZdlA4phq5HagPHZQ,5716
|
|
6
|
+
horsies/core/cli.py,sha256=irlRO9pbpGPZT7r0hSi1d0mEQkAB5M54zLb-6oB6GnY,21078
|
|
7
|
+
horsies/core/errors.py,sha256=3jcu4TcI1P9d2w-Tgz1yTsh4NFPOqvrd5S58cnUEKjE,16768
|
|
8
|
+
horsies/core/logging.py,sha256=p2utHDeOHgvwtSzKTKXh0RwUNCk38ODmgQQkmsD1tWA,2934
|
|
9
|
+
horsies/core/task_decorator.py,sha256=72qNptYCFs0NkVEOl24UtkCu1gAYHd79XfLYcXs37H4,29083
|
|
10
|
+
horsies/core/brokers/__init__.py,sha256=a_5xkHhRKY-1PRq6UidMBGq1bX-zeuSdxIvI6GdSiSQ,94
|
|
11
|
+
horsies/core/brokers/listener.py,sha256=pxnnAgLAWqODPV_J0XwUqAhBSrHstL77PSHYEfGoVhc,18637
|
|
12
|
+
horsies/core/brokers/postgres.py,sha256=KAX4oT42ku7o46R7FxoDr_jQ76zBSuN1_Za5s2QAvOk,38111
|
|
13
|
+
horsies/core/codec/serde.py,sha256=-dUEwyE4-DcAnP2CTbJYxBW3rOzXIUMsX_OSZSjNnpU,20043
|
|
14
|
+
horsies/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
horsies/core/models/app.py,sha256=d0SABV777m3UgiFCN56ZqfViagP67Kc822Ictfem4ek,12402
|
|
16
|
+
horsies/core/models/broker.py,sha256=yNf-tKSvJP1oIDis-jZwFMsT5exDCe27oAcnLRFSrRA,3041
|
|
17
|
+
horsies/core/models/queues.py,sha256=brB8rk_PQZds_VgavpZH3fkgqzsBAvRTySNNAtzikSw,699
|
|
18
|
+
horsies/core/models/recovery.py,sha256=9N7RYOVHewcK1Z_dUiRj4AAUtoQAOhCNLY9vXGFr7KU,5117
|
|
19
|
+
horsies/core/models/schedule.py,sha256=PLjxgMD_jFoLWCftuf9lVGfYhBqPHfM1FUpYguOkeWg,8284
|
|
20
|
+
horsies/core/models/task_pg.py,sha256=v5U1huJwASh2Saf8GDaRX-IM4mXcD8Fu43kfrFSFDLg,12304
|
|
21
|
+
horsies/core/models/tasks.py,sha256=qFyAZbxRAIhFLJqPgtlXO1es4eUnNzhL2X9rEKedkKA,11593
|
|
22
|
+
horsies/core/models/workflow.py,sha256=hv5FW-cWdH6hfC2t3rfJFf9__As4o33NYH3j8Ru7_r8,82079
|
|
23
|
+
horsies/core/models/workflow_pg.py,sha256=TM26UTtRC2-ulfDeEm0USRp3NYa0XDuFKGrCtmrajSg,9337
|
|
24
|
+
horsies/core/registry/tasks.py,sha256=mm1xb-f2HLUcZJrLgx2ZS-FQtCkuJbfsR-SW2qiMsts,3708
|
|
25
|
+
horsies/core/scheduler/__init__.py,sha256=m0GqCrdTbQNDUV1Fn3UZD5IewAYsV5olMrDRolg3a1I,699
|
|
26
|
+
horsies/core/scheduler/calculator.py,sha256=F3_9WoDKKq-1WH9Gkkne9HE4QnC_CtfsmoONPyIaXIU,8821
|
|
27
|
+
horsies/core/scheduler/service.py,sha256=xc0hhXlwUT3R-nIiqPzQKgYgrKHOo6qaHb5Js9J0jTw,21778
|
|
28
|
+
horsies/core/scheduler/state.py,sha256=B_oksOquP7rcinnRliMPEL5pAOzrdNegV-p4-VnDrYY,9222
|
|
29
|
+
horsies/core/types/status.py,sha256=p-SC0F-ay4oM97EqV8wHS0QimHsvftjPP1ExwheSrkA,1096
|
|
30
|
+
horsies/core/utils/imports.py,sha256=Tt6GTpIwGf3bIp9TZ9pYUFOS-9p_Y9cupaSLzZDLFcM,6457
|
|
31
|
+
horsies/core/utils/loop_runner.py,sha256=0DlbmtD8TefhyAg9edaZBHEvmze-sDj1behLKjuH4xY,1460
|
|
32
|
+
horsies/core/worker/current.py,sha256=UBkgevE9ulG4LFJoDlEXgpaco34qbV9Esk0tYdaBbMo,415
|
|
33
|
+
horsies/core/worker/worker.py,sha256=DJzkDCOo2eck7ht0QCA5cCElRvNvUJH1TiHiHis6abg,76240
|
|
34
|
+
horsies/core/workflows/__init__.py,sha256=JA-8wcYUHp-wF5OCEqJwKElT8PaZZB1G7iglDZ7WNiI,579
|
|
35
|
+
horsies/core/workflows/engine.py,sha256=ByIpZnPhQvjydVZSAJuV3Yj6eLzDfZQ3KHtPw93VojI,85894
|
|
36
|
+
horsies/core/workflows/recovery.py,sha256=x-jhYwIZpBxByYyFb5g2vDMtjmUFt33HgOB2J6mgPfs,19377
|
|
37
|
+
horsies/core/workflows/registry.py,sha256=ItpjTN8yK9NNSV8Q8OwDnHdQSO-hxDzxeWAyGvExpRk,3194
|
|
38
|
+
horsies-0.1.0a5.dist-info/METADATA,sha256=rCIhp6_TP-PZEhX630Sv970eChRIyCEldmlVXEQPtFw,1321
|
|
39
|
+
horsies-0.1.0a5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
40
|
+
horsies-0.1.0a5.dist-info/entry_points.txt,sha256=6b_OhuNbJ1ky9WSOt3UfNQXETG2YuH0lKimcibILrEE,50
|
|
41
|
+
horsies-0.1.0a5.dist-info/top_level.txt,sha256=ZQ_NurgT-yr3pE4a1svlO_VAhGJ6NFA31vJDLT1yyOA,8
|
|
42
|
+
horsies-0.1.0a5.dist-info/RECORD,,
|
horsies-0.1.0a3.dist-info/RECORD
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
horsies/__init__.py,sha256=rLxes5IuyzggohtXKRsHoddeyqEy0LsgwDlk09itKXk,2769
|
|
2
|
-
horsies/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
horsies/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
horsies/core/app.py,sha256=sRLVto5oFH0Mb3cwrzJXd2WoPEjoYL2ByJc764DW238,21523
|
|
5
|
-
horsies/core/banner.py,sha256=eCp8crZZWS6JjWoTn6RtCTEpvGmkpr7hWke_Av5-bvI,5716
|
|
6
|
-
horsies/core/cli.py,sha256=XPMRRvZqNg3ph96gWCfo0NJDOsqqe0Cuj_8Wx80sQP0,21057
|
|
7
|
-
horsies/core/errors.py,sha256=M0PMSdsqbOMPOsZsEOmfKTvxdAD5E7BZ0IuDus7HWtk,16640
|
|
8
|
-
horsies/core/logging.py,sha256=p2utHDeOHgvwtSzKTKXh0RwUNCk38ODmgQQkmsD1tWA,2934
|
|
9
|
-
horsies/core/task_decorator.py,sha256=mqQYV8iLt9TwsrNQDDsBx3Vo8T5HeLFv6iiWbhF3l-c,23799
|
|
10
|
-
horsies/core/brokers/__init__.py,sha256=a_5xkHhRKY-1PRq6UidMBGq1bX-zeuSdxIvI6GdSiSQ,94
|
|
11
|
-
horsies/core/brokers/listener.py,sha256=pxnnAgLAWqODPV_J0XwUqAhBSrHstL77PSHYEfGoVhc,18637
|
|
12
|
-
horsies/core/brokers/postgres.py,sha256=YZSJiQY1Spc2pNF8fjPy_0K9EAD9WAxVnFaV2P6Qf5Q,39073
|
|
13
|
-
horsies/core/codec/serde.py,sha256=-dUEwyE4-DcAnP2CTbJYxBW3rOzXIUMsX_OSZSjNnpU,20043
|
|
14
|
-
horsies/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
horsies/core/models/app.py,sha256=I3HKztjNzii75bDwlbB7Jl9CPrFMfjLInUJRacQ39_s,11863
|
|
16
|
-
horsies/core/models/broker.py,sha256=yNf-tKSvJP1oIDis-jZwFMsT5exDCe27oAcnLRFSrRA,3041
|
|
17
|
-
horsies/core/models/queues.py,sha256=brB8rk_PQZds_VgavpZH3fkgqzsBAvRTySNNAtzikSw,699
|
|
18
|
-
horsies/core/models/recovery.py,sha256=sI9h7Kbx_Jv23-J_0fTZRrrMgwNqmkX0-dWKTnpikvA,4964
|
|
19
|
-
horsies/core/models/schedule.py,sha256=5mCMsX-12OPz6Gs-nr_GtaJgjxDzuyjuhSTil2XIq50,8113
|
|
20
|
-
horsies/core/models/task_pg.py,sha256=v5U1huJwASh2Saf8GDaRX-IM4mXcD8Fu43kfrFSFDLg,12304
|
|
21
|
-
horsies/core/models/tasks.py,sha256=nQb5z8S-GioO2aZATEvfubzqKtsNBGu-RcCn9kdituw,11592
|
|
22
|
-
horsies/core/models/workflow.py,sha256=_93sQ-He9gbHJ4zOpmjlQ0pcRUPWGEXqzwKUNmN9gRc,71757
|
|
23
|
-
horsies/core/models/workflow_pg.py,sha256=Nd6JDy4Htft9qQ4xKW4U2vhKNe-uUMYYZfg14t-yJnk,9315
|
|
24
|
-
horsies/core/registry/tasks.py,sha256=mm1xb-f2HLUcZJrLgx2ZS-FQtCkuJbfsR-SW2qiMsts,3708
|
|
25
|
-
horsies/core/scheduler/__init__.py,sha256=m0GqCrdTbQNDUV1Fn3UZD5IewAYsV5olMrDRolg3a1I,699
|
|
26
|
-
horsies/core/scheduler/calculator.py,sha256=F3_9WoDKKq-1WH9Gkkne9HE4QnC_CtfsmoONPyIaXIU,8821
|
|
27
|
-
horsies/core/scheduler/service.py,sha256=DNixYHbEcztHHrOK3Ud_tVIfLyFJrujCyHlqN-peBpI,21711
|
|
28
|
-
horsies/core/scheduler/state.py,sha256=PAG2buNI2_jCIIFz4ofU4EaOTKbyNtHStM8vlmvRpIc,9211
|
|
29
|
-
horsies/core/types/status.py,sha256=lxepSeVJjYvAl6kaCuaeHjHf-_IrXhsfGfm1kpFkSUc,1074
|
|
30
|
-
horsies/core/utils/imports.py,sha256=srIgHxxQeguMK2pNr6gama7AIqSB2embJlPpaor6hyI,6457
|
|
31
|
-
horsies/core/utils/loop_runner.py,sha256=0DlbmtD8TefhyAg9edaZBHEvmze-sDj1behLKjuH4xY,1460
|
|
32
|
-
horsies/core/worker/current.py,sha256=UBkgevE9ulG4LFJoDlEXgpaco34qbV9Esk0tYdaBbMo,415
|
|
33
|
-
horsies/core/worker/worker.py,sha256=POhtTwvTjNogaXkBv4uOSD3ogb5LvuQz86A2jWmyW7I,76900
|
|
34
|
-
horsies/core/workflows/__init__.py,sha256=JA-8wcYUHp-wF5OCEqJwKElT8PaZZB1G7iglDZ7WNiI,579
|
|
35
|
-
horsies/core/workflows/engine.py,sha256=8Gjmch52g1IObKqzGVMWxZaZDC0diB6TuAtRfqr67l0,86927
|
|
36
|
-
horsies/core/workflows/recovery.py,sha256=xRKQPiYaLcX4vLuvcNGy5cV14eW4Cp_sewD1nXv8gc8,19445
|
|
37
|
-
horsies/core/workflows/registry.py,sha256=ItpjTN8yK9NNSV8Q8OwDnHdQSO-hxDzxeWAyGvExpRk,3194
|
|
38
|
-
horsies-0.1.0a3.dist-info/METADATA,sha256=O-mV7PXCaiEBJfxNVh3sbYZ3qnfWzUU3W6aPGgb_55c,1321
|
|
39
|
-
horsies-0.1.0a3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
40
|
-
horsies-0.1.0a3.dist-info/entry_points.txt,sha256=6b_OhuNbJ1ky9WSOt3UfNQXETG2YuH0lKimcibILrEE,50
|
|
41
|
-
horsies-0.1.0a3.dist-info/top_level.txt,sha256=ZQ_NurgT-yr3pE4a1svlO_VAhGJ6NFA31vJDLT1yyOA,8
|
|
42
|
-
horsies-0.1.0a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|