All docs
Multiple outputs
Emit several tables from one node with outputs=[...] and wire each with step.output('name'). Add graph_outputs for live read-only scalars on the card.
By default a step returns one Table on one output port. Two features change that: outputs=[...]
declares several named data ports, and graph_outputs={...} declares read-only scalar
metrics shown on the node card.
Usage#
from lsdtools import Tool, Table
tool = Tool("mesh")
@tool.load(outputs=["vertices", "faces"])
def make_mesh(n: int = 8) -> dict:
"""Produce two tables from one node."""
return {"vertices": build_vertices(n), "faces": build_faces(n)}
# → the node shows two output ports: vertices, faces
With outputs=[...] the function returns a dict whose keys match the declared names, each
mapping to a Table. This works on @tool.load and @tool.shape.
Wiring a specific output#
Downstream, select a port with step.output("name") on the node handle and pass it into the next
node's input:
m = make_mesh(n=8)
render_points(m.output("vertices")) # wire just the vertices port
build_index(m.output("faces")) # …and the faces port elsewhere
In a Python run, RunResult.outputs gives you every port as a {name: Table} dict; .output is
the primary one. See Engine.
Read-only metrics: graph_outputs#
graph_outputs={"name": type} declares small scalars that surface live on the node card — a row
count, a bounds string, a checksum. They are not data ports. Set them inside the run with
ctx.set_output(...) from an injected StepContext:
from lsdtools import Tool, Table, StepContext
@tool.shape(graph_outputs={"n_rows": int, "n_dropped": int})
def drop_nulls(t: Table, column: str, ctx: StepContext = None) -> Table:
kept = t.filter(t[column].is_valid())
ctx.set_output("n_rows", len(kept))
ctx.set_output("n_dropped", len(t) - len(kept))
return kept
# → the node card shows n_rows and n_dropped after each run
Because graph_outputs are metrics about a run rather than inputs to it, they are excluded from
the cache key — reporting a count never invalidates a cached result.
Combining both#
A step can declare data ports and metrics at once — return the dict of tables and set the scalars:
@tool.shape(outputs=["keep", "reject"], graph_outputs={"kept": int})
def partition(t: Table, floor: float = 0.0, ctx: StepContext = None) -> dict:
mask = t["score"] >= floor
keep, reject = t.filter(mask), t.filter(~mask)
ctx.set_output("kept", len(keep))
return {"keep": keep, "reject": reject}
Related#
Learn — Shape steps · Load steps · Parameters vs ports
Examples — Multi-output mesh · Live node metrics
Frequently asked questions
What is the difference between outputs and graph_outputs?
outputs=[...] declares named data ports — Tables you return in a dict and wire downstream. graph_outputs={...} declares small read-only scalars (a row count, a bounds string) that show on the node card but are not data ports and are excluded from the cache key.
How do I wire only one of a node's outputs?
Call step.output("name") on the node handle and pass that into the downstream port. A plain reference to the node uses its first/primary output.