All docs
Example: a complete package
The anatomy of a shippable package — a load, shape and deliver step, a CLI command, a dockable panel, and its lsd_package.json manifest.
Everything the other examples show, assembled into one shippable package: three pipeline steps, a CLI
command, and a dockable panel — plus the lsd_package.json manifest that makes
it installable. This is the shape to copy when you start a real package.
The code — tool.py#
# tool.py
from lsdtools import Tool, Context, Source, Table, ui
tool = Tool("assays", label="Assays")
# ── pipeline: load → shape → deliver ─────────────────────────────────────────
@tool.load(source_kinds=("file", "sql"))
def load_assays(src: Source) -> Table:
"""Load assay rows (needs columns: hole, grade, tonnes)."""
return src.read()
@tool.shape
def add_metal(t: Table, recovery: float = 0.9) -> Table:
"""Add a `metal` column = grade × tonnes × recovery."""
return t.with_column("metal", t["grade"] * t["tonnes"] * recovery)
@tool.deliver
def write_summary(t: Table, path: str = "by_hole.parquet") -> None:
"""Total metal and tonnes per hole → a Parquet file."""
t.group_by("hole").agg(metal="sum", tonnes="sum").write_parquet(path)
# ── a CLI command: lsd assays stats assays.csv ──────────────────────────────
@tool.command("stats", help="print recovered metal per hole for an assay CSV")
def stats(ctx: Context, src: str, recovery: float = 0.9) -> int:
"""Quick per-hole totals without opening the desktop."""
from lsdtools import Engine
metal = add_metal(load_assays(Source.file(src)), recovery=recovery)
Engine().run(metal).output.group_by("hole").agg(metal="sum", tonnes="sum").print()
return 0
# ── a dockable panel: a label + a live table of the add_metal output ─────────
@tool.view("assays.summary", title="Assay summary", location="sidebar", icon="lsd-chart-symbolic")
def summary_panel(ctx: Context):
"""A sidebar panel showing the current add_metal result."""
return ui.rows(
ui.label("Metal by hole"),
ui.table(**ui.bind.step("assays.add_metal")),
)
The manifest — lsd_package.json#
{
"_schema_version": 1,
"name": "lsd-assays",
"display_name": "Assays",
"version": "0.1.0",
"description": "Load assay CSVs, compute recovered metal, and summarise it per hole — with a CLI and a view.",
"author": "You",
"publisher": "you",
"keywords": ["mining", "assays", "example"],
"min_lsd": "0.1.0",
"dependencies": {},
"tool": "tool"
}
Folder layout — the manifest sits next to the code:
lsd-assays/
lsd_package.json
tool.py
What each piece does#
tool = Tool("assays")— oneToolper package, at module scope. Its name namespaces every node identity (assays.load_assays,assays.add_metal, …).- load → shape → deliver — the pipeline.
load_assaysproduces aTablefrom the picked source;add_metaltransforms it (t["grade"] * t["tonnes"] * recoveryis one whole-column expression);write_summaryaggregates per hole and writes Parquet. @tool.command("stats")— alsd assays stats <src>subcommand built from the signature:src(no default) is a positional,recovery(default) is--recovery. See a CLI command.@tool.view("assays.summary", location="sidebar")— a(ctx) -> Viewsidebar panel.ui.bind.step(...)binds the table view to theadd_metalnode's output, so it refreshes as the pipeline runs.- The manifest —
_schema_version: 1selects the flat v1 format.nameis the pip/distribution name;tool: "tool"points the loader at the module-leveltoolobject (use"pkg.mod:tool"when it lives deeper).min_lsdanddependenciesgate loading. See the full field table.
Run it#
Pipeline and deliver, from Python:
from lsdtools import Engine, Source, Table
Table({
"hole": ["DH1", "DH1", "DH2"],
"grade": [1.2, 0.8, 2.0],
"tonnes": [100.0, 150.0, 80.0],
}).write_csv("assays.csv")
metal = add_metal(load_assays(Source.file("assays.csv")), recovery=0.9)
Engine().run(write_summary(metal, path="by_hole.parquet"))
Table.read_parquet("by_hole.parquet").print()
# → hole metal tonnes
# DH1 216 250
# DH2 144 80
The same numbers straight from the CLI:
lsd assays stats assays.csv
# → hole metal tonnes
# DH1 216 250
# DH2 144 80
In the desktop, adding the package to a project surfaces the three nodes in the palette and the Assay summary panel in the sidebar.
Try changing it#
- Add a
dependenciesentry (e.g.{"lsd-spatial": ">=0.1.0"}) and the loader resolves it first. - Split the code into
steps.py+views.pyand set"code": "assays"to point at a package dir. - Publish it — scaffold with
lsd package new assays, then see Publish to the store.
Related#
Learn — Package anatomy · Develop a package · The Package Manager
API — lsd_package.json · Tool · CLI commands
Examples — A CLI command · A dockable panel · A node variant