loom-core 0.1.5__py3-none-any.whl → 0.1.7__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.
loom/core/context.py CHANGED
@@ -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 there are remaining events to replay, False otherwise
116
+ True if replaying historical events, False if executing new logic
102
117
  """
103
- return self.cursor < len(self.history)
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(
loom/core/logger.py CHANGED
@@ -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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loom-core
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Durable workflow orchestration engine for Python
5
5
  Home-page: https://github.com/satadeep3927/loom
6
6
  Author: Satadeep Dasgupta
@@ -7,10 +7,10 @@ loom/common/errors.py,sha256=jc7_XS8dUPS8fg5pb7kJaTuIR1e6eKBZyZtbsHW3lgM,1712
7
7
  loom/common/workflow.py,sha256=R4X1vt6duqTJz6kGk6YI866O-CleOiyw0dXdUVo_qAE,2118
8
8
  loom/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  loom/core/compiled.py,sha256=Y2NQDMZVLee65ILgJoK2Ny_OcR1T4LJnOQWQcyg0Z7U,1178
10
- loom/core/context.py,sha256=7zezfvPaPdyfjJnUd8cjTwoxKGUh0N6F1nHn8dCuUVY,9494
10
+ loom/core/context.py,sha256=IsEKaQV_Zzis-6HHLp79LgIZ5IEQvlkg7F8-ODYnVvM,10924
11
11
  loom/core/engine.py,sha256=6EvacrZCfVhJQ7yPurRdCeTzlGRaGk5uI-s7yxu9IUs,5822
12
12
  loom/core/handle.py,sha256=beOVmJit-gSrk7II4c2KuSubmr5OIPls82_vJ-pI04U,5532
13
- loom/core/logger.py,sha256=4ej95IdHOdBh-y7PQoRFz_H22PJYDXvAioBoId8mOhg,1729
13
+ loom/core/logger.py,sha256=ZSOZydQ-o8bfhh8VBcSWZNVE-NBvEedN7LDcbAEVJL0,1829
14
14
  loom/core/runner.py,sha256=nNA-EcdJt3UEgwzKHyb22xD7aLT8kI01x5UVdLteSI8,1998
15
15
  loom/core/state.py,sha256=oXiZ442PL2kl_JyQXLZOFeBLs_cTF2iEjykKVVUNiAI,2997
16
16
  loom/core/worker.py,sha256=jpTtvM1rIToVkG4SwJu1W9Q-eL3bw8b3N8KsxxrRovA,5322
@@ -42,9 +42,9 @@ loom/schemas/database.py,sha256=DlMBvacsJ9oceBHkrMiJC7uD3_rCHFe6Kwa2STFcKOw,283
42
42
  loom/schemas/events.py,sha256=Gz-R836nVXNSsp4Y54idhKs_WTvzFPmx5KlA8PunP28,2216
43
43
  loom/schemas/tasks.py,sha256=DJbLInggIGxI-CfP1kSyK79Bz83e3sZyEKh6C2HE8q4,1341
44
44
  loom/schemas/workflow.py,sha256=F1cNEJRuEWLjPhGinEZE2T6vwchDC8RuTHmxEIaH6Ls,671
45
- loom_core-0.1.5.dist-info/licenses/LICENSE,sha256=8EpC-clAYRUfJQ92T3iQEIIWYjx2A3Kfk28zOd8lh7I,1095
46
- loom_core-0.1.5.dist-info/METADATA,sha256=TdEYynBj66M4a173vlAyY5YAteLmBNVKEiqSuWZXx3Q,8897
47
- loom_core-0.1.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
- loom_core-0.1.5.dist-info/entry_points.txt,sha256=Jx5HXHL2y7jvSjkwkH3QqF954cbSxiE6OGwL2coldyE,42
49
- loom_core-0.1.5.dist-info/top_level.txt,sha256=cAfRgAuCuit-cU9iBrf0bS4ovvmq-URykNd9fmYMojg,5
50
- loom_core-0.1.5.dist-info/RECORD,,
45
+ loom_core-0.1.7.dist-info/licenses/LICENSE,sha256=8EpC-clAYRUfJQ92T3iQEIIWYjx2A3Kfk28zOd8lh7I,1095
46
+ loom_core-0.1.7.dist-info/METADATA,sha256=jxnYEA02G3sXiR4BQ4FWpXGuRIz7NDEux43KwWjyoMk,8897
47
+ loom_core-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
48
+ loom_core-0.1.7.dist-info/entry_points.txt,sha256=Jx5HXHL2y7jvSjkwkH3QqF954cbSxiE6OGwL2coldyE,42
49
+ loom_core-0.1.7.dist-info/top_level.txt,sha256=cAfRgAuCuit-cU9iBrf0bS4ovvmq-URykNd9fmYMojg,5
50
+ loom_core-0.1.7.dist-info/RECORD,,