commandnet 0.4.1__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.1 → commandnet-0.5.0}/PKG-INFO +1 -1
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/core/models.py +0 -1
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/engine/runtime.py +48 -18
- {commandnet-0.4.1 → commandnet-0.5.0}/pyproject.toml +1 -1
- {commandnet-0.4.1 → commandnet-0.5.0}/README.md +0 -0
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/__init__.py +0 -0
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/core/graph.py +0 -0
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/core/node.py +0 -0
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/interfaces/event_bus.py +0 -0
- {commandnet-0.4.1 → commandnet-0.5.0}/commandnet/interfaces/observer.py +0 -0
- {commandnet-0.4.1 → 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)
|
|
@@ -165,7 +165,6 @@ class Engine:
|
|
|
165
165
|
key = subject_id.split("#")[1]
|
|
166
166
|
waiters = await self.db.resolve_call_group(key)
|
|
167
167
|
for w in waiters:
|
|
168
|
-
# Resume waiters using the final context of the shared node as payload
|
|
169
168
|
await self._apply_target(
|
|
170
169
|
w["subject_id"],
|
|
171
170
|
w["context"],
|
|
@@ -191,18 +190,23 @@ class Engine:
|
|
|
191
190
|
node_name = target.get_node_name()
|
|
192
191
|
await self.observer.on_transition(subject_id, "RUN", node_name, duration)
|
|
193
192
|
|
|
193
|
+
# PROPAGATION: Use provided payload
|
|
194
194
|
p_load = payload.model_dump() if hasattr(payload, "model_dump") else payload
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
evt = Event(
|
|
197
|
+
subject_id=subject_id,
|
|
198
|
+
node_name=node_name,
|
|
199
|
+
payload=p_load,
|
|
200
|
+
)
|
|
201
201
|
ctx_dict = self._dump_ctx(context)
|
|
202
202
|
|
|
203
203
|
if "#" in subject_id:
|
|
204
204
|
await self.db.save_sub_state(
|
|
205
|
-
subject_id,
|
|
205
|
+
subject_id,
|
|
206
|
+
subject_id.split("#")[0],
|
|
207
|
+
node_name,
|
|
208
|
+
ctx_dict,
|
|
209
|
+
evt,
|
|
206
210
|
)
|
|
207
211
|
else:
|
|
208
212
|
await self.db.save_state(subject_id, node_name, ctx_dict, evt)
|
|
@@ -214,6 +218,7 @@ class Engine:
|
|
|
214
218
|
if isinstance(target, Wait):
|
|
215
219
|
actual_id = subject_id
|
|
216
220
|
actual_ctx = context
|
|
221
|
+
|
|
217
222
|
if target.sub_context_path and "#" not in subject_id:
|
|
218
223
|
actual_id = f"{subject_id}#{target.sub_context_path}"
|
|
219
224
|
actual_ctx = self._get_path(context, target.sub_context_path)
|
|
@@ -221,6 +226,7 @@ class Engine:
|
|
|
221
226
|
await self.observer.on_transition(
|
|
222
227
|
actual_id, "RUN", f"WAIT:{target.signal_id}", duration
|
|
223
228
|
)
|
|
229
|
+
|
|
224
230
|
await self.db.park_subject(
|
|
225
231
|
actual_id,
|
|
226
232
|
target.signal_id,
|
|
@@ -231,7 +237,12 @@ class Engine:
|
|
|
231
237
|
|
|
232
238
|
# 6. Parallel Fan-out
|
|
233
239
|
if isinstance(target, Parallel):
|
|
234
|
-
join_name =
|
|
240
|
+
join_name = (
|
|
241
|
+
target.join_node.get_node_name()
|
|
242
|
+
if target.join_node
|
|
243
|
+
else "FORK"
|
|
244
|
+
)
|
|
245
|
+
|
|
235
246
|
await self.observer.on_transition(
|
|
236
247
|
subject_id, "RUN", f"PARALLEL:{join_name}", duration
|
|
237
248
|
)
|
|
@@ -242,11 +253,10 @@ class Engine:
|
|
|
242
253
|
)
|
|
243
254
|
|
|
244
255
|
for branch in target.branches:
|
|
245
|
-
# Normalize branch
|
|
256
|
+
# Normalize branch into ParallelTask
|
|
246
257
|
if isinstance(branch, ParallelTask):
|
|
247
258
|
task = branch
|
|
248
259
|
else:
|
|
249
|
-
# If it's a Wait or a Node class, wrap it but keep the action intact
|
|
250
260
|
path = (
|
|
251
261
|
branch.sub_context_path
|
|
252
262
|
if isinstance(branch, Wait) and branch.sub_context_path
|
|
@@ -254,30 +264,42 @@ class Engine:
|
|
|
254
264
|
)
|
|
255
265
|
task = ParallelTask(action=branch, sub_context_path=path)
|
|
256
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
|
+
|
|
257
272
|
sub_ctx = self._get_path(context, task.sub_context_path)
|
|
273
|
+
|
|
258
274
|
await self._apply_target(
|
|
259
275
|
f"{subject_id}#{task.sub_context_path}",
|
|
260
276
|
sub_ctx,
|
|
261
277
|
task.action,
|
|
262
|
-
payload=
|
|
278
|
+
payload=branch_payload,
|
|
263
279
|
)
|
|
264
280
|
|
|
265
281
|
if target.join_node:
|
|
266
282
|
await self.db.save_state(
|
|
267
|
-
subject_id,
|
|
283
|
+
subject_id,
|
|
284
|
+
"WAITING_FOR_JOIN",
|
|
285
|
+
self._dump_ctx(context),
|
|
286
|
+
None,
|
|
268
287
|
)
|
|
269
288
|
else:
|
|
270
289
|
await self._apply_target(subject_id, context, None)
|
|
290
|
+
|
|
271
291
|
return
|
|
272
292
|
|
|
273
293
|
# 7. Schedule (Delayed Execution)
|
|
274
294
|
if isinstance(target, Schedule):
|
|
275
295
|
if not (
|
|
276
|
-
isinstance(target.action, type)
|
|
296
|
+
isinstance(target.action, type)
|
|
297
|
+
and issubclass(target.action, Node)
|
|
277
298
|
):
|
|
278
299
|
raise TypeError("Schedule.action must be a Node class.")
|
|
279
300
|
|
|
280
301
|
target_node_name = target.action.get_node_name()
|
|
302
|
+
|
|
281
303
|
await self.observer.on_transition(
|
|
282
304
|
subject_id, "RUN", f"SCHEDULED:{target_node_name}", duration
|
|
283
305
|
)
|
|
@@ -285,10 +307,16 @@ class Engine:
|
|
|
285
307
|
run_at_dt = datetime.now(timezone.utc) + timedelta(
|
|
286
308
|
seconds=target.delay_seconds
|
|
287
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
|
+
|
|
288
316
|
p_load = (
|
|
289
|
-
|
|
290
|
-
if hasattr(
|
|
291
|
-
else
|
|
317
|
+
final_payload.model_dump()
|
|
318
|
+
if hasattr(final_payload, "model_dump")
|
|
319
|
+
else final_payload
|
|
292
320
|
)
|
|
293
321
|
|
|
294
322
|
scheduled_evt = Event(
|
|
@@ -301,10 +329,12 @@ class Engine:
|
|
|
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
|