nodus-flow
Status: v0.2.0 — renamed from
nodus-workflow. See Naming.
A standalone asyncio DAG runner with WAIT/RESUME, priority scheduling and rehydration. Define DAGs, execute them with priority-queued scheduling, suspend nodes on events, and rehydrate WAITING runs after a process restart. No required external dependencies — pure stdlib.
Naming
This is not the engine behind the Nodus workflow keyword. That engine
ships inside nodus-lang and is not separately installable:
workflow build {
step compile { ... }
step test after compile { ... }
}
If that is what you are looking for, pip install nodus-lang — nothing on this
page will run it.
nodus-flow is a Python library with its own vocabulary (FlowDefinition,
FlowNode, FlowRun, FlowExecutor) and its own execution model. It shares no
code with nodus-lang and does not depend on it. Its design came from
aindy-runtime rather than from Nodus, which is why it reads like a second
workflow engine — it is one, for a different host.
Why the rename. Published as nodus-workflow, the name read as "the Nodus
workflow implementation" and misled a source-level architecture audit of Nodus
into reporting that the project had "forked its own thesis" — a wrong
top-priority finding that reached the governance record and stood for months.
The name was the whole cause: the auditor never read the PyPI metadata, and an
earlier rename of the import name had not reached them either. Tracked as
nodus-lang#483.
pip install nodus-workflow still works and now installs this package, but the
old name is deprecated and will not receive releases.
Install
pip install nodus-flow
What it provides
| Component | Purpose |
|---|---|
FlowDefinition |
DAG: nodes, edges, default retry, timeout |
FlowNode |
One node with handler_id, config, optional retry override |
FlowEdge |
Directed edge with optional condition function |
FlowRun / InMemoryRunStore |
Run state + thread-safe in-memory store |
SchedulerEngine |
Priority queue (high/normal/low) + WAIT/RESUME |
FlowExecutor |
Orchestrates start(), resume(), handler registration |
FlowRehydrator |
Re-registers WAITING runs after process restart |
WorkflowWaitSignal |
Raise inside a handler to suspend node execution |
Quick start
import asyncio
from nodus_flow import (
FlowDefinition, FlowNode, FlowEdge, FlowExecutor,
InMemoryRunStore, SchedulerEngine,
)
# Define handlers
async def fetch_data(ctx):
return {"data": "fetched"}
async def process_data(ctx):
return {"processed": ctx["state"].get("data")}
# Build the DAG
flow = FlowDefinition(
name="my-pipeline",
nodes=[
FlowNode(id="fetch", handler_id="fetch_data"),
FlowNode(id="process", handler_id="process_data"),
],
edges=[
FlowEdge(from_node="fetch", to_node="process"),
],
)
# Execute
store = InMemoryRunStore()
scheduler = SchedulerEngine()
executor = FlowExecutor(store=store, scheduler=scheduler)
executor.register_handler("fetch_data", fetch_data)
executor.register_handler("process_data", process_data)
run = await executor.start(flow, initial_state={})
print(run.status) # FlowStatus.COMPLETED
WAIT/RESUME semantics
from nodus_flow import WorkflowWaitSignal
async def approval_node(ctx):
raise WorkflowWaitSignal(
event_type="approval.granted",
correlation_key=ctx["run_id"],
)
# Later, when the event fires:
await executor.resume(run_id, event_payload={"approver": "alice"})
When a node raises WorkflowWaitSignal, the run transitions to WAITING
and is parked in the scheduler until notify_event or resume is called.
SchedulerEngine
from nodus_flow import SchedulerEngine
from nodus_flow.run import FlowStatus
scheduler = SchedulerEngine()
# Schedule with priority
scheduler.schedule(run_id, priority="high") # high / normal / low
scheduler.schedule(run_id, priority="normal")
next_run_id = scheduler.pop() # returns highest-priority pending run | None
# WAIT/RESUME
scheduler.wait_for_event(run_id, event_type="approval.granted", key="k")
scheduler.notify_event(event_type="approval.granted", key="k") # re-queues run
scheduler.cancel_wait(run_id)
FlowRehydrator
from nodus_flow import FlowRehydrator, InMemoryRunStore
store = InMemoryRunStore()
rehydrator = FlowRehydrator(store=store, scheduler=scheduler)
# On process startup — re-register all WAITING runs
rehydrator.rehydrate()
FlowStatus transitions
PENDING → RUNNING → WAITING → (event fires) → EXECUTING → COMPLETED
↘ FAILED
Design
- No required dependencies. Pure stdlib (
asyncio,threading,heapq,dataclasses,datetime,uuid). - Protocol-based handlers. Any async callable
(context: dict) → dictsatisfiesNodeHandler. - Thread-safe.
SchedulerEngineandInMemoryRunStoreusethreading.Lock. - Separate from nodus-lang. No nodus-lang import required — use it standalone or inside any Python application. See Naming for what this does not do.
Development
pip install -e ".[dev]"
pytest tests/ -q
License
MIT — see LICENSE.
Metadata
Release files for nodus-flow 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nodus_flow-0.2.0.tar.gz | 14.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nodus_flow-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.9 kB
Release files / nodus_flow-0.2.0.tar.gz
| Download URL | nodus_flow-0.2.0.tar.gz |
|---|---|
| Size | 14.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4c5c44e340080c2d2c743bdfa8863583bb8a84aec005ac51a3e60eca09be9bb6
|
|
BLAKE2b-256 checksum How to use checksums |
203b9a9681a18280f9f0729dfb28beb9fd5b8cad8ea9a0f5e11a18260a7acea9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / nodus_flow-0.2.0-py3-none-any.whl
| Download URL | nodus_flow-0.2.0-py3-none-any.whl |
|---|---|
| Size | 13.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ca706ac2b91072dc84203a7c60383f970f29941d1a3e9528abf20a8a999de120
|
|
BLAKE2b-256 checksum How to use checksums |
a469a41bd5bc6d84b357bb59c38f5ff15e2ebe4d44caafaad17708b12a5b8151
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|