Python API
Complete Python API documentation for HORUS. The Python bindings are built with PyO3, exposing the core Rust framework to Python with zero-copy shared memory IPC.
Python Bindings
Full reference for the HORUS Python bindings:
Node— Create nodes withtick,init, andshutdowncallbacksTopic— Unified pub/sub with typed messages (CmdVel, Pose2D, Imu, etc.)Scheduler— Node execution with priority ordering and rate controlSchedulerconfiguration —tick_rate,rt,deterministic,watchdog_ms,blackbox_mb,recording,name,cores,max_deadline_misses,verbose,telemetry,netkeyword argumentsTransformFrame/Transform— Coordinate frame managementTensorPool/Tensor— Zero-copy shared memory tensors;.numpy()and.torch()share the buffer via the array interface
Custom Messages
Create your own typed messages in Python:
- Dict topics — no build step:
node.send('robot.status', {'battery': 85, 'ok': True}),node.recv('robot.status')returns a dict - Compiled messages —
horus.msggen.register_message()+build_messages()(runsmaturin develop --release, needs a horus_py source checkout). A real Rust type with named fields, but it still travels over the generic MessagePack path, not the zero-copy backend the built-in types use — see Custom Messages - YAML schema support for team projects
Async Nodes
Asynchronous Python nodes for non-blocking I/O operations.
Quick Reference
Core Classes
| Class | Description |
|---|---|
Node | Computation unit with tick/init/shutdown callbacks |
Topic | Unified pub/sub channel with typed messages |
Scheduler | Node execution orchestrator |
Scheduler kwargs | Keyword-only constructor arguments: tick_rate (default 1000.0), rt, deterministic, watchdog_ms, blackbox_mb, recording, name, cores, max_deadline_misses, verbose, telemetry, net. horus.run() accepts the same set |
NodeState | Node lifecycle states |
Params | Runtime key-value configuration — see Params deep dive |
Rate | Drift-compensated pacing for loops you drive yourself — see Rate |
drivers | Hardware driver registry: horus.drivers.register_driver() and horus.drivers.load() — see Write a driver |
There is no importable horus.NodeInfo or horus.Priority — neither name is part of the public Python API. Runtime context and logging reach you through the node object passed to init / tick / shutdown: node.log_info(), node.log_warning(), node.log_error(), node.log_debug() (these only emit while the scheduler is driving the node). Node(order=...) sets the registration priority, and Node(priority=...) is the SCHED_FIFO real-time priority (1-99, where higher is more urgent).
order does not sequence ticks in Python. Every Python node carries a rate — 30 Hz by default — which makes it real-time and gives it its own thread, so nodes run concurrently regardless of order. It sequences ticks only under deterministic=True.
Runtime Helpers
| Name | Description |
|---|---|
budget_remaining() | Seconds left in this tick's budget — for a node that wants to bail out before overrunning |
rng_float() | Random float in [0.0, 1.0) from the deterministic per-tick RNG |
timestamp_ns() | Current time in nanoseconds, for stamping messages |
Exceptions
| Name | Raised when |
|---|---|
HorusNotFoundError | A topic, frame or node is not found |
HorusTimeoutError | A blocking operation times out |
HorusTransformError | A coordinate transform fails (extrapolation, stale data) |
Message Types
| Class | Fields | Default Topic |
|---|---|---|
CmdVel | linear, angular | "cmd_vel" |
Pose2D | x, y, theta | "pose" |
Imu | accel_x/y/z, gyro_x/y/z | "imu" |
Odometry | x, y, theta, linear_velocity, angular_velocity | "odom" |
LaserScan | ranges, angle_min/max, range_min/max | "scan" |
Transform System
| Class | Description |
|---|---|
TransformFrame | Coordinate frame tree with transform lookups |
Transform | 3D transformation (translation + quaternion rotation) |
TransformFrameConfig | TransformFrame configuration with size presets |
Perception Types
These live in the horus.perception submodule, which must be imported explicitly —
import horus alone does not bind it, so horus.perception.Detection raises
AttributeError unless you import the submodule first:
import horus.perception as perception # or: from horus.perception import Detection
| Class | Description |
|---|---|
Detection | Object detection result (bbox, class, confidence) |
DetectionList | Collection of detections with filtering |
BoundingBox2D | 2D bounding box with IoU calculation |
PointCloudBuffer | Point cloud with NumPy integration |
TrackedObject | Object tracking with velocity estimation |
Landmark | 2D landmark/keypoint for pose estimation |
COCOPose | COCO keypoint indices |
PointXYZ | 3D point (x, y, z) |
PointXYZRGB | 3D point with RGB color |
Detection, BoundingBox2D, TrackedObject and Landmark each name two different
classes. from horus import Detection is the wire message, which is what a topic carries;
from horus.perception import Detection is the helper listed above, which is what the
analysis methods live on. They are not interchangeable, and the failure reads as a
contradiction: horus.Detection is horus.perception.Detection is False, and passing one
where the other is expected raises TypeError: 'Detection' object is not an instance of 'Detection'. DetectionList accepts either.
Tensor System
| Class | Description |
|---|---|
TensorPool | Shared memory tensor pool allocation |
Tensor | Zero-copy tensor with NumPy/PyTorch interop |
Networking
The Python bindings do not do networking. Both of the APIs that look like they should are inert:
| API | What actually happens |
|---|---|
Topic(msg, endpoint="topic@host:port") | The @ and everything after it is discarded; you get an ordinary shared-memory topic named topic. No socket is opened. |
Scheduler(net=True) / horus.run(..., net=True) | Accepted, then dropped. At run() the wrapper looks for a native replicator hook that the module does not export, emits a RuntimeWarning, and runs without replication. |
The net=True warning is a RuntimeWarning, and Python does print it: the default
filters restrict only DeprecationWarning to __main__, so a RuntimeWarning raised
from library code shows on stderr once per call site with no -W flag needed. The
hazard is milder than being silent, but real — one line in a service's log at startup
is easy to miss, and the process then runs without replication for as long as it
lives. Grep your startup log for network replication is not available if you depend
on net=True.
Cross-machine replication is a Rust-side feature: build with --features net or use
horus run --net, and let the replicator mirror the shared-memory topics your Python
nodes already publish to. See
Network Backends.
Installation
pip install "horus-robotics>=0.4.0"
The floor is not optional. The newest horus-robotics on PyPI is 0.1.9, and
it speaks an older shared-memory format than a 0.4.x CLI — so a bare
pip install horus-robotics gets you a package that imports, runs, and whose
nodes publish topics the CLI cannot see. Nothing reports it; horus topic list
simply shows nothing. The floor turns that silence into a resolver error.
Until 0.4.x reaches PyPI, install from the tree the HORUS installer already cached — same tag as your CLI, by construction:
pip install ~/.horus/cache/horus@0.4.0/horus_py
horus --version prints the version to substitute. See
Python Support for the
build-from-source route.
Minimal Example
import horus
def my_tick(node):
node.send("greeting", "Hello from Python!")
msg = node.recv("greeting")
if msg:
print(msg)
node = horus.Node(
name="MyNode",
pubs=["greeting"],
subs=["greeting"],
tick=my_tick,
rate=10
)
horus.run(node)