loom-core 0.1.5__tar.gz → 0.1.7__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.5 → loom_core-0.1.7}/PKG-INFO +1 -1
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/context.py +36 -5
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/logger.py +2 -1
- {loom_core-0.1.5 → loom_core-0.1.7}/loom_core.egg-info/PKG-INFO +1 -1
- {loom_core-0.1.5 → loom_core-0.1.7}/pyproject.toml +1 -1
- {loom_core-0.1.5 → loom_core-0.1.7}/LICENSE +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/MANIFEST.in +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/QUICKSTART.md +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/README.md +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/__init__.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/cli/__init__.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/cli/cli.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/common/activity.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/common/config.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/common/errors.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/common/workflow.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/__init__.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/compiled.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/engine.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/handle.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/runner.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/state.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/worker.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/core/workflow.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/database/__init__.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/database/db.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/decorators/__init__.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/decorators/activity.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/decorators/workflow.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/lib/progress.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/lib/utils.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/001_setup_pragma.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/002_create_workflows.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/003.create_events.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/004.create_tasks.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/005.create_indexes.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/006_auto_update_triggers.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/down/007_create_logs.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/001_setup_pragma.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/002_create_workflows.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/003_create_events.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/004_create_tasks.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/005_create_indexes.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/006_auto_update_triggers.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/migrations/up/007_create_logs.sql +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/schemas/__init__.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/schemas/activity.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/schemas/database.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/schemas/events.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/schemas/tasks.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom/schemas/workflow.py +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom_core.egg-info/SOURCES.txt +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom_core.egg-info/dependency_links.txt +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom_core.egg-info/entry_points.txt +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom_core.egg-info/requires.txt +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/loom_core.egg-info/top_level.txt +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/setup.cfg +0 -0
- {loom_core-0.1.5 → loom_core-0.1.7}/setup.py +0 -0
|
@@ -37,6 +37,7 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
37
37
|
state: StateProxy[InputT, StateT]
|
|
38
38
|
cursor: int = 0
|
|
39
39
|
logger: WorkflowLogger
|
|
40
|
+
_original_history_length: int # Track original history size for replay detection
|
|
40
41
|
|
|
41
42
|
def __init__(
|
|
42
43
|
self, id: str, input: InputT, history: List[Event], state: StateT
|
|
@@ -52,6 +53,7 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
52
53
|
self.id = id
|
|
53
54
|
self.input = input
|
|
54
55
|
self.history = history
|
|
56
|
+
self._original_history_length = len(history) # Remember original size
|
|
55
57
|
self.state = StateProxy(self, state)
|
|
56
58
|
self.logger = WorkflowLogger(self)
|
|
57
59
|
|
|
@@ -82,6 +84,19 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
82
84
|
self.cursor += 1
|
|
83
85
|
return event
|
|
84
86
|
|
|
87
|
+
def _skip_step_events(self) -> None:
|
|
88
|
+
"""Skip over STEP_START and STEP_END events during replay.
|
|
89
|
+
|
|
90
|
+
These are internal workflow management events that don't affect
|
|
91
|
+
the deterministic execution logic.
|
|
92
|
+
"""
|
|
93
|
+
while True:
|
|
94
|
+
event = self._peek()
|
|
95
|
+
if event and event["type"] in ("STEP_START", "STEP_END"):
|
|
96
|
+
self._consume()
|
|
97
|
+
else:
|
|
98
|
+
break
|
|
99
|
+
|
|
85
100
|
def _match_event(self, expected_type: str) -> Event | None:
|
|
86
101
|
"""
|
|
87
102
|
Safe helper to check if the NEXT event matches what we expect.
|
|
@@ -95,14 +110,13 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
95
110
|
|
|
96
111
|
@property
|
|
97
112
|
def is_replaying(self) -> bool:
|
|
98
|
-
"""Check if the workflow is currently replaying events.
|
|
113
|
+
"""Check if the workflow is currently replaying old events.
|
|
99
114
|
|
|
100
115
|
Returns:
|
|
101
|
-
True if
|
|
116
|
+
True if replaying historical events, False if executing new logic
|
|
102
117
|
"""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@property
|
|
118
|
+
# We're replaying if we haven't consumed all the original events yet
|
|
119
|
+
return self.cursor < self._original_history_length
|
|
106
120
|
def is_at_end_of_history(self) -> bool:
|
|
107
121
|
"""Check if we've consumed all events in history.
|
|
108
122
|
|
|
@@ -145,6 +159,8 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
145
159
|
) -> FuncReturn:
|
|
146
160
|
metadata = self._extract_activity_metadata(fn, args)
|
|
147
161
|
|
|
162
|
+
# Skip any step events first
|
|
163
|
+
self._skip_step_events()
|
|
148
164
|
scheduled_event = self._match_event("ACTIVITY_SCHEDULED")
|
|
149
165
|
|
|
150
166
|
if scheduled_event:
|
|
@@ -156,6 +172,8 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
156
172
|
|
|
157
173
|
self._consume()
|
|
158
174
|
|
|
175
|
+
# Skip step events before checking for completion
|
|
176
|
+
self._skip_step_events()
|
|
159
177
|
completed_event = self._match_event("ACTIVITY_COMPLETED")
|
|
160
178
|
|
|
161
179
|
if completed_event:
|
|
@@ -164,6 +182,8 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
164
182
|
|
|
165
183
|
raise StopReplay
|
|
166
184
|
|
|
185
|
+
# Skip step events before checking for unexpected events
|
|
186
|
+
self._skip_step_events()
|
|
167
187
|
unexpected_event = self._peek()
|
|
168
188
|
if unexpected_event:
|
|
169
189
|
raise NonDeterministicWorkflowError(
|
|
@@ -189,11 +209,16 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
189
209
|
datetime.datetime.now(datetime.timezone.utc) + delta if delta else until # type: ignore
|
|
190
210
|
)
|
|
191
211
|
|
|
212
|
+
# Skip any step events first
|
|
213
|
+
self._skip_step_events()
|
|
214
|
+
|
|
192
215
|
scheduled_event = self._match_event("TIMER_SCHEDULED")
|
|
193
216
|
|
|
194
217
|
if scheduled_event:
|
|
195
218
|
self._consume()
|
|
196
219
|
|
|
220
|
+
# Skip step events before checking for timer fired
|
|
221
|
+
self._skip_step_events()
|
|
197
222
|
fired_event = self._match_event("TIMER_FIRED")
|
|
198
223
|
if fired_event:
|
|
199
224
|
self._consume()
|
|
@@ -201,6 +226,8 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
201
226
|
|
|
202
227
|
raise StopReplay
|
|
203
228
|
|
|
229
|
+
# Skip step events before checking for unexpected events
|
|
230
|
+
self._skip_step_events()
|
|
204
231
|
unexpected_event = self._peek()
|
|
205
232
|
if unexpected_event:
|
|
206
233
|
raise NonDeterministicWorkflowError(
|
|
@@ -219,6 +246,8 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
219
246
|
If the signal is already in history (replay), it returns the data immediately.
|
|
220
247
|
If not, it raises StopReplay to suspend execution until the signal arrives.
|
|
221
248
|
"""
|
|
249
|
+
# Skip any step events first
|
|
250
|
+
self._skip_step_events()
|
|
222
251
|
# 1. Check if the signal is next in history
|
|
223
252
|
event = self._match_event("SIGNAL_RECEIVED")
|
|
224
253
|
|
|
@@ -235,6 +264,8 @@ class WorkflowContext(Generic[InputT, StateT]):
|
|
|
235
264
|
self._consume()
|
|
236
265
|
return event["payload"]["data"]
|
|
237
266
|
|
|
267
|
+
# Skip step events before checking for unexpected events
|
|
268
|
+
self._skip_step_events()
|
|
238
269
|
unexpected_event = self._peek()
|
|
239
270
|
if unexpected_event:
|
|
240
271
|
raise NonDeterministicWorkflowError(
|
|
@@ -40,10 +40,11 @@ class WorkflowLogger:
|
|
|
40
40
|
self._log("DEBUG", msg)
|
|
41
41
|
|
|
42
42
|
def _log(self, level: str, msg: str):
|
|
43
|
+
# Only suppress logging if we're actually replaying historical events
|
|
43
44
|
if self._ctx.is_replaying:
|
|
44
45
|
return
|
|
45
46
|
|
|
46
|
-
self._std_logger.info(f"{msg}")
|
|
47
|
+
self._std_logger.info(f"[{self._ctx.id[:8]}] {msg}")
|
|
47
48
|
asyncio.create_task(self._write_log_to_db(level, msg))
|
|
48
49
|
|
|
49
50
|
async def _write_log_to_db(self, level: str, msg: str):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|