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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nodus_flow-0.2.0.tar.gz.
File metadata
- Download URL: nodus_flow-0.2.0.tar.gz
- Upload date:
- Size: 14.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c5c44e340080c2d2c743bdfa8863583bb8a84aec005ac51a3e60eca09be9bb6
|
|
| MD5 |
a81329b68b686ef5be6ea0508882a574
|
|
| BLAKE2b-256 |
203b9a9681a18280f9f0729dfb28beb9fd5b8cad8ea9a0f5e11a18260a7acea9
|
File details
Details for the file nodus_flow-0.2.0-py3-none-any.whl.
File metadata
- Download URL: nodus_flow-0.2.0-py3-none-any.whl
- Upload date:
- Size: 13.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca706ac2b91072dc84203a7c60383f970f29941d1a3e9528abf20a8a999de120
|
|
| MD5 |
cef5efd2f78695335b8910bec7bf1c37
|
|
| BLAKE2b-256 |
a469a41bd5bc6d84b357bb59c38f5ff15e2ebe4d44caafaad17708b12a5b8151
|