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.
Files changed (61) hide show
  1. stabilize/__init__.py +29 -0
  2. stabilize/cli.py +1193 -0
  3. stabilize/context/__init__.py +7 -0
  4. stabilize/context/stage_context.py +170 -0
  5. stabilize/dag/__init__.py +15 -0
  6. stabilize/dag/graph.py +215 -0
  7. stabilize/dag/topological.py +199 -0
  8. stabilize/examples/__init__.py +1 -0
  9. stabilize/examples/docker-example.py +759 -0
  10. stabilize/examples/golden-standard-expected-result.txt +1 -0
  11. stabilize/examples/golden-standard.py +488 -0
  12. stabilize/examples/http-example.py +606 -0
  13. stabilize/examples/llama-example.py +662 -0
  14. stabilize/examples/python-example.py +731 -0
  15. stabilize/examples/shell-example.py +399 -0
  16. stabilize/examples/ssh-example.py +603 -0
  17. stabilize/handlers/__init__.py +53 -0
  18. stabilize/handlers/base.py +226 -0
  19. stabilize/handlers/complete_stage.py +209 -0
  20. stabilize/handlers/complete_task.py +75 -0
  21. stabilize/handlers/complete_workflow.py +150 -0
  22. stabilize/handlers/run_task.py +369 -0
  23. stabilize/handlers/start_stage.py +262 -0
  24. stabilize/handlers/start_task.py +74 -0
  25. stabilize/handlers/start_workflow.py +136 -0
  26. stabilize/launcher.py +307 -0
  27. stabilize/migrations/01KDQ4N9QPJ6Q4MCV3V9GHWPV4_initial_schema.sql +97 -0
  28. stabilize/migrations/01KDRK3TXW4R2GERC1WBCQYJGG_rag_embeddings.sql +25 -0
  29. stabilize/migrations/__init__.py +1 -0
  30. stabilize/models/__init__.py +15 -0
  31. stabilize/models/stage.py +389 -0
  32. stabilize/models/status.py +146 -0
  33. stabilize/models/task.py +125 -0
  34. stabilize/models/workflow.py +317 -0
  35. stabilize/orchestrator.py +113 -0
  36. stabilize/persistence/__init__.py +28 -0
  37. stabilize/persistence/connection.py +185 -0
  38. stabilize/persistence/factory.py +136 -0
  39. stabilize/persistence/memory.py +214 -0
  40. stabilize/persistence/postgres.py +655 -0
  41. stabilize/persistence/sqlite.py +674 -0
  42. stabilize/persistence/store.py +235 -0
  43. stabilize/queue/__init__.py +59 -0
  44. stabilize/queue/messages.py +377 -0
  45. stabilize/queue/processor.py +312 -0
  46. stabilize/queue/queue.py +526 -0
  47. stabilize/queue/sqlite_queue.py +354 -0
  48. stabilize/rag/__init__.py +19 -0
  49. stabilize/rag/assistant.py +459 -0
  50. stabilize/rag/cache.py +294 -0
  51. stabilize/stages/__init__.py +11 -0
  52. stabilize/stages/builder.py +253 -0
  53. stabilize/tasks/__init__.py +19 -0
  54. stabilize/tasks/interface.py +335 -0
  55. stabilize/tasks/registry.py +255 -0
  56. stabilize/tasks/result.py +283 -0
  57. stabilize-0.9.2.dist-info/METADATA +301 -0
  58. stabilize-0.9.2.dist-info/RECORD +61 -0
  59. stabilize-0.9.2.dist-info/WHEEL +4 -0
  60. stabilize-0.9.2.dist-info/entry_points.txt +2 -0
  61. stabilize-0.9.2.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,235 @@
1
+ """
2
+ WorkflowStore interface.
3
+
4
+ This module defines the abstract interface for execution persistence.
5
+ All storage backends must implement this interface.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from collections.abc import Iterator
12
+ from dataclasses import dataclass
13
+ from typing import TYPE_CHECKING
14
+
15
+ from stabilize.models.status import WorkflowStatus
16
+
17
+ if TYPE_CHECKING:
18
+ from stabilize.models.stage import StageExecution
19
+ from stabilize.models.workflow import Workflow
20
+
21
+
22
+ class WorkflowNotFoundError(Exception):
23
+ """Raised when an execution cannot be found."""
24
+
25
+ def __init__(self, execution_id: str):
26
+ self.execution_id = execution_id
27
+ super().__init__(f"Execution not found: {execution_id}")
28
+
29
+
30
+ @dataclass
31
+ class WorkflowCriteria:
32
+ """Criteria for querying executions."""
33
+
34
+ page_size: int = 20
35
+ statuses: set[WorkflowStatus] | None = None
36
+ start_time_before: int | None = None
37
+ start_time_after: int | None = None
38
+
39
+
40
+ class WorkflowStore(ABC):
41
+ """Abstract interface for execution persistence."""
42
+
43
+ # ========== Execution Operations ==========
44
+
45
+ @abstractmethod
46
+ def store(self, execution: Workflow) -> None:
47
+ """
48
+ Store a complete execution.
49
+
50
+ Creates the execution and all its stages.
51
+
52
+ Args:
53
+ execution: The execution to store
54
+ """
55
+ pass
56
+
57
+ @abstractmethod
58
+ def retrieve(self, execution_id: str) -> Workflow:
59
+ """
60
+ Retrieve an execution by ID.
61
+
62
+ Args:
63
+ execution_id: The execution ID
64
+
65
+ Returns:
66
+ The execution
67
+
68
+ Raises:
69
+ WorkflowNotFoundError: If not found
70
+ """
71
+ pass
72
+
73
+ @abstractmethod
74
+ def update_status(self, execution: Workflow) -> None:
75
+ """
76
+ Update the status of an execution.
77
+
78
+ Args:
79
+ execution: The execution with updated status
80
+ """
81
+ pass
82
+
83
+ @abstractmethod
84
+ def delete(self, execution_id: str) -> None:
85
+ """
86
+ Delete an execution and all its stages.
87
+
88
+ Args:
89
+ execution_id: The execution ID
90
+ """
91
+ pass
92
+
93
+ # ========== Stage Operations ==========
94
+
95
+ @abstractmethod
96
+ def store_stage(self, stage: StageExecution) -> None:
97
+ """
98
+ Store or update a stage.
99
+
100
+ Args:
101
+ stage: The stage to store
102
+ """
103
+ pass
104
+
105
+ @abstractmethod
106
+ def add_stage(self, stage: StageExecution) -> None:
107
+ """
108
+ Add a new stage to an execution.
109
+
110
+ Args:
111
+ stage: The stage to add
112
+ """
113
+ pass
114
+
115
+ @abstractmethod
116
+ def remove_stage(
117
+ self,
118
+ execution: Workflow,
119
+ stage_id: str,
120
+ ) -> None:
121
+ """
122
+ Remove a stage from an execution.
123
+
124
+ Args:
125
+ execution: The execution
126
+ stage_id: The stage ID to remove
127
+ """
128
+ pass
129
+
130
+ # ========== Query Operations ==========
131
+
132
+ @abstractmethod
133
+ def retrieve_by_pipeline_config_id(
134
+ self,
135
+ pipeline_config_id: str,
136
+ criteria: WorkflowCriteria | None = None,
137
+ ) -> Iterator[Workflow]:
138
+ """
139
+ Retrieve executions by pipeline config ID.
140
+
141
+ Args:
142
+ pipeline_config_id: The pipeline config ID
143
+ criteria: Optional query criteria
144
+
145
+ Returns:
146
+ Iterator of matching executions
147
+ """
148
+ pass
149
+
150
+ @abstractmethod
151
+ def retrieve_by_application(
152
+ self,
153
+ application: str,
154
+ criteria: WorkflowCriteria | None = None,
155
+ ) -> Iterator[Workflow]:
156
+ """
157
+ Retrieve executions by application.
158
+
159
+ Args:
160
+ application: The application name
161
+ criteria: Optional query criteria
162
+
163
+ Returns:
164
+ Iterator of matching executions
165
+ """
166
+ pass
167
+
168
+ # ========== Pause/Resume Operations ==========
169
+
170
+ @abstractmethod
171
+ def pause(
172
+ self,
173
+ execution_id: str,
174
+ paused_by: str,
175
+ ) -> None:
176
+ """
177
+ Pause an execution.
178
+
179
+ Args:
180
+ execution_id: The execution ID
181
+ paused_by: Who paused it
182
+ """
183
+ pass
184
+
185
+ @abstractmethod
186
+ def resume(self, execution_id: str) -> None:
187
+ """
188
+ Resume a paused execution.
189
+
190
+ Args:
191
+ execution_id: The execution ID
192
+ """
193
+ pass
194
+
195
+ # ========== Cancel Operations ==========
196
+
197
+ @abstractmethod
198
+ def cancel(
199
+ self,
200
+ execution_id: str,
201
+ canceled_by: str,
202
+ reason: str,
203
+ ) -> None:
204
+ """
205
+ Cancel an execution.
206
+
207
+ Args:
208
+ execution_id: The execution ID
209
+ canceled_by: Who canceled it
210
+ reason: Cancellation reason
211
+ """
212
+ pass
213
+
214
+ # ========== Optional Methods ==========
215
+
216
+ def is_healthy(self) -> bool:
217
+ """
218
+ Check if the repository is healthy.
219
+
220
+ Returns:
221
+ True if healthy
222
+ """
223
+ return True
224
+
225
+ def count_by_application(self, application: str) -> int:
226
+ """
227
+ Count executions for an application.
228
+
229
+ Args:
230
+ application: The application name
231
+
232
+ Returns:
233
+ Number of executions
234
+ """
235
+ return sum(1 for _ in self.retrieve_by_application(application))
@@ -0,0 +1,59 @@
1
+ """Message queue system for pipeline execution."""
2
+
3
+ from stabilize.queue.messages import (
4
+ CancelStage,
5
+ CancelWorkflow,
6
+ CompleteStage,
7
+ CompleteTask,
8
+ CompleteWorkflow,
9
+ ContinueParentStage,
10
+ InvalidStageId,
11
+ InvalidTaskId,
12
+ InvalidTaskType,
13
+ InvalidWorkflowId,
14
+ Message,
15
+ PauseTask,
16
+ RestartStage,
17
+ ResumeStage,
18
+ RunTask,
19
+ SkipStage,
20
+ StageLevel,
21
+ StartStage,
22
+ StartTask,
23
+ StartWorkflow,
24
+ TaskLevel,
25
+ WorkflowLevel,
26
+ )
27
+ from stabilize.queue.queue import InMemoryQueue, PostgresQueue, Queue
28
+ from stabilize.queue.sqlite_queue import SqliteQueue
29
+
30
+ __all__ = [
31
+ # Message types
32
+ "Message",
33
+ "WorkflowLevel",
34
+ "StageLevel",
35
+ "TaskLevel",
36
+ "StartWorkflow",
37
+ "StartStage",
38
+ "StartTask",
39
+ "RunTask",
40
+ "CompleteTask",
41
+ "CompleteStage",
42
+ "CompleteWorkflow",
43
+ "CancelWorkflow",
44
+ "CancelStage",
45
+ "SkipStage",
46
+ "PauseTask",
47
+ "ResumeStage",
48
+ "ContinueParentStage",
49
+ "RestartStage",
50
+ "InvalidWorkflowId",
51
+ "InvalidStageId",
52
+ "InvalidTaskId",
53
+ "InvalidTaskType",
54
+ # Queue implementations
55
+ "Queue",
56
+ "InMemoryQueue",
57
+ "PostgresQueue",
58
+ "SqliteQueue",
59
+ ]
@@ -0,0 +1,377 @@
1
+ """
2
+ Message types for the queue-based execution engine.
3
+
4
+ This module defines all message types used in the pipeline execution queue.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime
11
+ from typing import Any
12
+
13
+ from stabilize.models.stage import SyntheticStageOwner
14
+ from stabilize.models.status import WorkflowStatus
15
+
16
+
17
+ @dataclass
18
+ class Message:
19
+ """
20
+ Base class for all queue messages.
21
+
22
+ Each message includes metadata for tracking and debugging.
23
+ """
24
+
25
+ # Message metadata
26
+ message_id: str | None = field(default=None, repr=False)
27
+ created_at: datetime = field(default_factory=datetime.now, repr=False)
28
+ attempts: int = field(default=0, repr=False)
29
+ max_attempts: int = field(default=10, repr=False)
30
+
31
+ def copy_with_attempts(self, attempts: int) -> Message:
32
+ """Create a copy with updated attempt count."""
33
+ import copy
34
+
35
+ new_msg = copy.copy(self)
36
+ new_msg.attempts = attempts
37
+ return new_msg
38
+
39
+
40
+ # ============================================================================
41
+ # Execution-level messages
42
+ # ============================================================================
43
+
44
+
45
+ @dataclass
46
+ class WorkflowLevel(Message):
47
+ """
48
+ Base class for execution-level messages.
49
+
50
+ These messages target a specific pipeline execution.
51
+ """
52
+
53
+ execution_type: str = "PIPELINE"
54
+ execution_id: str = ""
55
+
56
+
57
+ @dataclass
58
+ class StartWorkflow(WorkflowLevel):
59
+ """
60
+ Message to start a pipeline execution.
61
+
62
+ Triggers the beginning of pipeline execution, starting initial stages.
63
+ """
64
+
65
+ pass
66
+
67
+
68
+ @dataclass
69
+ class CompleteWorkflow(WorkflowLevel):
70
+ """
71
+ Message to complete a pipeline execution.
72
+
73
+ Sent when all stages have completed or execution should be finalized.
74
+ """
75
+
76
+ pass
77
+
78
+
79
+ @dataclass
80
+ class CancelWorkflow(WorkflowLevel):
81
+ """
82
+ Message to cancel a pipeline execution.
83
+
84
+ Marks the execution as canceled and stops all running stages.
85
+ """
86
+
87
+ user: str = ""
88
+ reason: str = ""
89
+
90
+
91
+ @dataclass
92
+ class StartWaitingWorkflows(Message):
93
+ """
94
+ Message to start any queued/waiting executions for a pipeline config.
95
+
96
+ Sent after an execution completes when concurrent execution limits are enabled.
97
+ """
98
+
99
+ pipeline_config_id: str = ""
100
+ purge_queue: bool = False
101
+
102
+
103
+ # ============================================================================
104
+ # Stage-level messages
105
+ # ============================================================================
106
+
107
+
108
+ @dataclass
109
+ class StageLevel(WorkflowLevel):
110
+ """
111
+ Base class for stage-level messages.
112
+
113
+ These messages target a specific stage within an execution.
114
+ """
115
+
116
+ stage_id: str = ""
117
+
118
+ @classmethod
119
+ def from_execution_level(cls, msg: WorkflowLevel, stage_id: str) -> StageLevel:
120
+ """Create a stage-level message from an execution-level message."""
121
+ return cls(
122
+ execution_type=msg.execution_type,
123
+ execution_id=msg.execution_id,
124
+ stage_id=stage_id,
125
+ )
126
+
127
+
128
+ @dataclass
129
+ class StartStage(StageLevel):
130
+ """
131
+ Message to start a stage.
132
+
133
+ Checks if upstream stages are complete, then plans and starts the stage.
134
+ """
135
+
136
+ pass
137
+
138
+
139
+ @dataclass
140
+ class CompleteStage(StageLevel):
141
+ """
142
+ Message to complete a stage.
143
+
144
+ Determines stage status, plans after stages, and triggers downstream.
145
+ """
146
+
147
+ pass
148
+
149
+
150
+ @dataclass
151
+ class SkipStage(StageLevel):
152
+ """
153
+ Message to skip a stage.
154
+
155
+ Sets stage status to SKIPPED and triggers downstream stages.
156
+ """
157
+
158
+ pass
159
+
160
+
161
+ @dataclass
162
+ class CancelStage(StageLevel):
163
+ """
164
+ Message to cancel a stage.
165
+
166
+ Cancels any running tasks and marks stage as canceled.
167
+ """
168
+
169
+ pass
170
+
171
+
172
+ @dataclass
173
+ class RestartStage(StageLevel):
174
+ """
175
+ Message to restart a stage.
176
+
177
+ Resets stage status and re-executes from the beginning.
178
+ """
179
+
180
+ pass
181
+
182
+
183
+ @dataclass
184
+ class ResumeStage(StageLevel):
185
+ """
186
+ Message to resume a paused stage.
187
+
188
+ Continues execution from where it was paused.
189
+ """
190
+
191
+ pass
192
+
193
+
194
+ @dataclass
195
+ class ContinueParentStage(StageLevel):
196
+ """
197
+ Message to continue parent stage after synthetic stage completes.
198
+
199
+ Sent when a synthetic stage completes to notify its parent.
200
+ """
201
+
202
+ phase: SyntheticStageOwner = SyntheticStageOwner.STAGE_AFTER
203
+
204
+
205
+ # ============================================================================
206
+ # Task-level messages
207
+ # ============================================================================
208
+
209
+
210
+ @dataclass
211
+ class TaskLevel(StageLevel):
212
+ """
213
+ Base class for task-level messages.
214
+
215
+ These messages target a specific task within a stage.
216
+ """
217
+
218
+ task_id: str = ""
219
+
220
+ @classmethod
221
+ def from_stage_level(cls, msg: StageLevel, task_id: str) -> TaskLevel:
222
+ """Create a task-level message from a stage-level message."""
223
+ return cls(
224
+ execution_type=msg.execution_type,
225
+ execution_id=msg.execution_id,
226
+ stage_id=msg.stage_id,
227
+ task_id=task_id,
228
+ )
229
+
230
+
231
+ @dataclass
232
+ class StartTask(TaskLevel):
233
+ """
234
+ Message to start a task.
235
+
236
+ Sets task status to RUNNING and triggers RunTask.
237
+ """
238
+
239
+ pass
240
+
241
+
242
+ @dataclass
243
+ class RunTask(TaskLevel):
244
+ """
245
+ Message to execute a task.
246
+
247
+ Runs the task implementation and handles the result.
248
+ """
249
+
250
+ task_type: str = ""
251
+
252
+
253
+ @dataclass
254
+ class CompleteTask(TaskLevel):
255
+ """
256
+ Message to complete a task.
257
+
258
+ Updates task status and triggers next task or stage completion.
259
+ """
260
+
261
+ status: WorkflowStatus = WorkflowStatus.SUCCEEDED
262
+ original_status: WorkflowStatus | None = None
263
+
264
+
265
+ @dataclass
266
+ class PauseTask(TaskLevel):
267
+ """
268
+ Message to pause a task.
269
+
270
+ Used when execution is paused - task will resume when execution resumes.
271
+ """
272
+
273
+ pass
274
+
275
+
276
+ # ============================================================================
277
+ # Error messages
278
+ # ============================================================================
279
+
280
+
281
+ @dataclass
282
+ class InvalidWorkflowId(WorkflowLevel):
283
+ """
284
+ Message indicating an invalid execution ID was referenced.
285
+
286
+ Logged and dropped - no further processing.
287
+ """
288
+
289
+ pass
290
+
291
+
292
+ @dataclass
293
+ class InvalidStageId(StageLevel):
294
+ """
295
+ Message indicating an invalid stage ID was referenced.
296
+
297
+ Logged and dropped - no further processing.
298
+ """
299
+
300
+ pass
301
+
302
+
303
+ @dataclass
304
+ class InvalidTaskId(TaskLevel):
305
+ """
306
+ Message indicating an invalid task ID was referenced.
307
+
308
+ Logged and dropped - no further processing.
309
+ """
310
+
311
+ pass
312
+
313
+
314
+ @dataclass
315
+ class InvalidTaskType(TaskLevel):
316
+ """
317
+ Message indicating an unknown task type was referenced.
318
+
319
+ Logged and dropped - no further processing.
320
+ """
321
+
322
+ task_type_name: str = ""
323
+
324
+
325
+ # ============================================================================
326
+ # Message Registry
327
+ # ============================================================================
328
+
329
+
330
+ # Map of message type names to classes for deserialization
331
+ MESSAGE_TYPES: dict[str, type[Message]] = {
332
+ "StartWorkflow": StartWorkflow,
333
+ "CompleteWorkflow": CompleteWorkflow,
334
+ "CancelWorkflow": CancelWorkflow,
335
+ "StartWaitingWorkflows": StartWaitingWorkflows,
336
+ "StartStage": StartStage,
337
+ "CompleteStage": CompleteStage,
338
+ "SkipStage": SkipStage,
339
+ "CancelStage": CancelStage,
340
+ "RestartStage": RestartStage,
341
+ "ResumeStage": ResumeStage,
342
+ "ContinueParentStage": ContinueParentStage,
343
+ "StartTask": StartTask,
344
+ "RunTask": RunTask,
345
+ "CompleteTask": CompleteTask,
346
+ "PauseTask": PauseTask,
347
+ "InvalidWorkflowId": InvalidWorkflowId,
348
+ "InvalidStageId": InvalidStageId,
349
+ "InvalidTaskId": InvalidTaskId,
350
+ "InvalidTaskType": InvalidTaskType,
351
+ }
352
+
353
+
354
+ def get_message_type_name(message: Message) -> str:
355
+ """Get the type name for a message."""
356
+ return message.__class__.__name__
357
+
358
+
359
+ def create_message_from_dict(type_name: str, data: dict[str, Any]) -> Message:
360
+ """
361
+ Create a message from a dictionary representation.
362
+
363
+ Args:
364
+ type_name: The message type name
365
+ data: Dictionary of message fields
366
+
367
+ Returns:
368
+ A Message instance
369
+
370
+ Raises:
371
+ ValueError: If type_name is unknown
372
+ """
373
+ if type_name not in MESSAGE_TYPES:
374
+ raise ValueError(f"Unknown message type: {type_name}")
375
+
376
+ message_class = MESSAGE_TYPES[type_name]
377
+ return message_class(**data)