Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Examples/ Example: a complete package
Examples

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.

TL;DRA real, shippable package is one tool.py (a load + shape + deliver, a @tool.command, and a @tool.view) plus an lsd_package.json manifest. This walkthrough shows all of it together and how the pieces mount as nodes, a CLI subcommand, and a sidebar panel.

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#

Python
# 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#

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:

text
lsd-assays/
 lsd_package.json
 tool.py

What each piece does#

  • tool = Tool("assays") — one Tool per package, at module scope. Its name namespaces every node identity (assays.load_assays, assays.add_metal, …).
  • load → shape → deliver — the pipeline. load_assays produces a Table from the picked source; add_metal transforms it (t["grade"] * t["tonnes"] * recovery is one whole-column expression); write_summary aggregates per hole and writes Parquet.
  • @tool.command("stats") — a lsd 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) -> View sidebar panel. ui.bind.step(...) binds the table view to the add_metal node's output, so it refreshes as the pipeline runs.
  • The manifest_schema_version: 1 selects the flat v1 format. name is the pip/distribution name; tool: "tool" points the loader at the module-level tool object (use "pkg.mod:tool" when it lives deeper). min_lsd and dependencies gate loading. See the full field table.

Run it#

Pipeline and deliver, from Python:

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:

bash
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 dependencies entry (e.g. {"lsd-spatial": ">=0.1.0"}) and the loader resolves it first.
  • Split the code into steps.py + views.py and set "code": "assays" to point at a package dir.
  • Publish it — scaffold with lsd package new assays, then see Publish to the store.

LearnPackage anatomy · Develop a package · The Package Manager

APIlsd_package.json · Tool · CLI commands

ExamplesA CLI command · A dockable panel · A node variant

By LSD Team · Last updated Jul 18, 2026 Ask on Discord View as Markdown