commandnet 0.4.2__tar.gz → 0.5.0__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.
- {commandnet-0.4.2 → commandnet-0.5.0}/PKG-INFO +1 -1
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/core/models.py +0 -1
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/engine/runtime.py +48 -18
- {commandnet-0.4.2 → commandnet-0.5.0}/pyproject.toml +1 -1
- {commandnet-0.4.2 → commandnet-0.5.0}/README.md +0 -0
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/__init__.py +0 -0
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/core/graph.py +0 -0
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/core/node.py +0 -0
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/interfaces/event_bus.py +0 -0
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/interfaces/observer.py +0 -0
- {commandnet-0.4.2 → commandnet-0.5.0}/commandnet/interfaces/persistence.py +0 -0
|
@@ -11,7 +11,6 @@ class Event(BaseModel):
|
|
|
11
11
|
subject_id: str
|
|
12
12
|
node_name: str
|
|
13
13
|
payload: Optional[Dict[str, Any]] = None
|
|
14
|
-
headers: Dict[str, str] = Field(default_factory=dict)
|
|
15
14
|
|
|
16
15
|
timestamp: str = Field(default_factory=utcnow_iso)
|
|
17
16
|
run_at: str = Field(default_factory=utcnow_iso)
|
|
@@ -127,9 +127,6 @@ class Engine:
|
|
|
127
127
|
duration: float = 0.0,
|
|
128
128
|
payload: Any = None,
|
|
129
129
|
):
|
|
130
|
-
headers = {}
|
|
131
|
-
if hasattr(context, "trace_headers") and context.trace_headers:
|
|
132
|
-
headers = context.trace_headers
|
|
133
130
|
# 1. Interrupt (Cancellation)
|
|
134
131
|
if isinstance(target, Interrupt):
|
|
135
132
|
await self.cancel_subject(target.subject_id, target.hard)
|
|
@@ -168,7 +165,6 @@ class Engine:
|
|
|
168
165
|
key = subject_id.split("#")[1]
|
|
169
166
|
waiters = await self.db.resolve_call_group(key)
|
|
170
167
|
for w in waiters:
|
|
171
|
-
# Resume waiters using the final context of the shared node as payload
|
|
172
168
|
await self._apply_target(
|
|
173
169
|
w["subject_id"],
|
|
174
170
|
w["context"],
|
|
@@ -194,14 +190,23 @@ class Engine:
|
|
|
194
190
|
node_name = target.get_node_name()
|
|
195
191
|
await self.observer.on_transition(subject_id, "RUN", node_name, duration)
|
|
196
192
|
|
|
193
|
+
# PROPAGATION: Use provided payload
|
|
197
194
|
p_load = payload.model_dump() if hasattr(payload, "model_dump") else payload
|
|
198
195
|
|
|
199
|
-
evt = Event(
|
|
196
|
+
evt = Event(
|
|
197
|
+
subject_id=subject_id,
|
|
198
|
+
node_name=node_name,
|
|
199
|
+
payload=p_load,
|
|
200
|
+
)
|
|
200
201
|
ctx_dict = self._dump_ctx(context)
|
|
201
202
|
|
|
202
203
|
if "#" in subject_id:
|
|
203
204
|
await self.db.save_sub_state(
|
|
204
|
-
subject_id,
|
|
205
|
+
subject_id,
|
|
206
|
+
subject_id.split("#")[0],
|
|
207
|
+
node_name,
|
|
208
|
+
ctx_dict,
|
|
209
|
+
evt,
|
|
205
210
|
)
|
|
206
211
|
else:
|
|
207
212
|
await self.db.save_state(subject_id, node_name, ctx_dict, evt)
|
|
@@ -213,6 +218,7 @@ class Engine:
|
|
|
213
218
|
if isinstance(target, Wait):
|
|
214
219
|
actual_id = subject_id
|
|
215
220
|
actual_ctx = context
|
|
221
|
+
|
|
216
222
|
if target.sub_context_path and "#" not in subject_id:
|
|
217
223
|
actual_id = f"{subject_id}#{target.sub_context_path}"
|
|
218
224
|
actual_ctx = self._get_path(context, target.sub_context_path)
|
|
@@ -220,6 +226,7 @@ class Engine:
|
|
|
220
226
|
await self.observer.on_transition(
|
|
221
227
|
actual_id, "RUN", f"WAIT:{target.signal_id}", duration
|
|
222
228
|
)
|
|
229
|
+
|
|
223
230
|
await self.db.park_subject(
|
|
224
231
|
actual_id,
|
|
225
232
|
target.signal_id,
|
|
@@ -230,7 +237,12 @@ class Engine:
|
|
|
230
237
|
|
|
231
238
|
# 6. Parallel Fan-out
|
|
232
239
|
if isinstance(target, Parallel):
|
|
233
|
-
join_name =
|
|
240
|
+
join_name = (
|
|
241
|
+
target.join_node.get_node_name()
|
|
242
|
+
if target.join_node
|
|
243
|
+
else "FORK"
|
|
244
|
+
)
|
|
245
|
+
|
|
234
246
|
await self.observer.on_transition(
|
|
235
247
|
subject_id, "RUN", f"PARALLEL:{join_name}", duration
|
|
236
248
|
)
|
|
@@ -241,11 +253,10 @@ class Engine:
|
|
|
241
253
|
)
|
|
242
254
|
|
|
243
255
|
for branch in target.branches:
|
|
244
|
-
# Normalize branch
|
|
256
|
+
# Normalize branch into ParallelTask
|
|
245
257
|
if isinstance(branch, ParallelTask):
|
|
246
258
|
task = branch
|
|
247
259
|
else:
|
|
248
|
-
# If it's a Wait or a Node class, wrap it but keep the action intact
|
|
249
260
|
path = (
|
|
250
261
|
branch.sub_context_path
|
|
251
262
|
if isinstance(branch, Wait) and branch.sub_context_path
|
|
@@ -253,30 +264,42 @@ class Engine:
|
|
|
253
264
|
)
|
|
254
265
|
task = ParallelTask(action=branch, sub_context_path=path)
|
|
255
266
|
|
|
267
|
+
# PROPAGATION: fallback to incoming payload if branch has none
|
|
268
|
+
branch_payload = (
|
|
269
|
+
task.payload if task.payload is not None else payload
|
|
270
|
+
)
|
|
271
|
+
|
|
256
272
|
sub_ctx = self._get_path(context, task.sub_context_path)
|
|
273
|
+
|
|
257
274
|
await self._apply_target(
|
|
258
275
|
f"{subject_id}#{task.sub_context_path}",
|
|
259
276
|
sub_ctx,
|
|
260
277
|
task.action,
|
|
261
|
-
payload=
|
|
278
|
+
payload=branch_payload,
|
|
262
279
|
)
|
|
263
280
|
|
|
264
281
|
if target.join_node:
|
|
265
282
|
await self.db.save_state(
|
|
266
|
-
subject_id,
|
|
283
|
+
subject_id,
|
|
284
|
+
"WAITING_FOR_JOIN",
|
|
285
|
+
self._dump_ctx(context),
|
|
286
|
+
None,
|
|
267
287
|
)
|
|
268
288
|
else:
|
|
269
289
|
await self._apply_target(subject_id, context, None)
|
|
290
|
+
|
|
270
291
|
return
|
|
271
292
|
|
|
272
293
|
# 7. Schedule (Delayed Execution)
|
|
273
294
|
if isinstance(target, Schedule):
|
|
274
295
|
if not (
|
|
275
|
-
isinstance(target.action, type)
|
|
296
|
+
isinstance(target.action, type)
|
|
297
|
+
and issubclass(target.action, Node)
|
|
276
298
|
):
|
|
277
299
|
raise TypeError("Schedule.action must be a Node class.")
|
|
278
300
|
|
|
279
301
|
target_node_name = target.action.get_node_name()
|
|
302
|
+
|
|
280
303
|
await self.observer.on_transition(
|
|
281
304
|
subject_id, "RUN", f"SCHEDULED:{target_node_name}", duration
|
|
282
305
|
)
|
|
@@ -284,10 +307,16 @@ class Engine:
|
|
|
284
307
|
run_at_dt = datetime.now(timezone.utc) + timedelta(
|
|
285
308
|
seconds=target.delay_seconds
|
|
286
309
|
)
|
|
310
|
+
|
|
311
|
+
# PROPAGATION: fallback to incoming payload if schedule payload is missing
|
|
312
|
+
final_payload = (
|
|
313
|
+
target.payload if target.payload is not None else payload
|
|
314
|
+
)
|
|
315
|
+
|
|
287
316
|
p_load = (
|
|
288
|
-
|
|
289
|
-
if hasattr(
|
|
290
|
-
else
|
|
317
|
+
final_payload.model_dump()
|
|
318
|
+
if hasattr(final_payload, "model_dump")
|
|
319
|
+
else final_payload
|
|
291
320
|
)
|
|
292
321
|
|
|
293
322
|
scheduled_evt = Event(
|
|
@@ -296,15 +325,16 @@ class Engine:
|
|
|
296
325
|
payload=p_load,
|
|
297
326
|
run_at=run_at_dt.isoformat(),
|
|
298
327
|
idempotency_key=target.idempotency_key,
|
|
299
|
-
headers=headers,
|
|
300
328
|
)
|
|
301
329
|
|
|
302
330
|
await self.db.schedule_event(scheduled_evt)
|
|
303
331
|
await self.db.save_state(
|
|
304
|
-
subject_id,
|
|
332
|
+
subject_id,
|
|
333
|
+
target_node_name,
|
|
334
|
+
self._dump_ctx(context),
|
|
335
|
+
None,
|
|
305
336
|
)
|
|
306
337
|
return
|
|
307
|
-
|
|
308
338
|
# --- EXTERNAL CONTROL & SIGNALS ---
|
|
309
339
|
|
|
310
340
|
async def signal_node(self, subject_id: str, signal_id: str, payload: Any = None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "commandnet"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "A lightweight, Pydantic-powered, distributed event-driven state machine and typed node graph runtime."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Christopher Vaz", email = "christophervaz160@gmail.com" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|