from lsdtools import Tool, table, Table
tool = Tool("hello")
@tool.load # ← a node in the palette
def cities(count: int = 3) -> Table: # count → a form field
return table({
"city": ["Tokyo", "Delhi", "Lima", "Oslo"][:count],
"pop": [37_400_068, 31_181_376, 11_204_000, 717_710][:count],
})
@tool.shape # Table in → Table out
def top_cities(t: Table, limit: int = 10) -> Table:
"""t → an input port; limit → a form field."""
return (
t.filter(t["pop"] > 1_000_000)
.sort("pop", descending=True)
.head(limit)
)
from lsdtools import Engine
@tool.deliver # Table in → the outside world
def export(t: Table, name: str = "report"):
t.write_parquet(f"{name}.parquet")
if __name__ == "__main__":
Engine().run(export(top_cities(cities(count=4))))
# → report.parquet — every step cached, only changes re-run
from lsdtools import ui
dialog = ui.form(
ui.number("cutoff", label="Keep above"),
ui.select("mode", options=["fast", "exact"]),
rules={"cutoff": [ui.range(0, 100)]},
)
ui.validate(dialog, {"cutoff": 150}) # → {"cutoff": "must be ≤ 100"}
@tool.shape(dialog=dialog) # field names bind to params — checked at import
def keep(t: Table, cutoff: float = 50, mode: str = "fast") -> Table: ...
from lsdtools import Context
@tool.command("stats", help="Rows, columns, nulls — any CSV")
def stats(ctx: Context, src: str) -> int:
t = table.read_csv(src)
print(f"{len(t)} rows × {len(t.columns)} columns")
t.null_report().print()
return 0
# $ lsd hello stats people.csv → 3 rows × 2 columns