All docs
Example: clean a CSV
Load a CSV through a Source, drop bad rows, rename and sort columns, print a null report, then write a clean CSV — a full three-step pipeline.
The everyday job: read a messy CSV, tidy it, and write a clean one. This tool contains a
load, a shape, and a deliver step —
you can run it directly with the Python example below. For Desktop, put tool.py in a
manifest-backed Local package, link its source folder from the
source-development CLI, and enable it in Settings → Project Packages. Linked Local source
edits require a fresh Desktop process.
For project-only code, use steps.py beside the .lsd file and explicitly review it through
Enable project code…. Opening the project alone never loads neighboring scripts.
Run Once uses reviewed immutable bytes; Trust Folder & Enable Live Development is the
separate human choice that enables live reload of saved project-code edits.
The code#
# tool.py
from lsdtools import Tool, Source, Table
tool = Tool("csvclean", label="CSV Clean")
@tool.load(source_kinds=("file",))
def load_people(src: Source) -> Table:
"""Read whatever CSV/Parquet file the user picks."""
return src.read()
@tool.shape
def clean(t: Table, min_age: int = 0) -> Table:
"""Rename messy headers, drop rows under `min_age`, then sort by name."""
t = t.rename({"full_name": "name", "yrs": "age"})
t = t.filter(t["age"] >= min_age)
return t.sort("name")
@tool.deliver
def write_clean(t: Table, path: str = "clean.csv") -> None:
"""Log a per-column null report, then write the cleaned table to CSV."""
t.null_report().print()
t.write_csv(path)
What each line does#
@tool.load(source_kinds=("file",))— a node that produces data.src: Sourceis an injected reader bound to the file the user picks;src.read()returns aTable. Narrowingsource_kindsto"file"keeps the picker to files.@tool.shape— a transform.t: Tableis an input port (wire it from the load node);min_age: int = 0has a default, so it is an optional parameter and renders as a number field.t.rename({"full_name": "name", ...})— rename columns with an{old: new}map; unlisted columns pass through.t.filter(t["age"] >= min_age)—t["age"] >= min_ageis a booleanColumn;filterkeeps the rows where it isTrue.@tool.deliver— a sink.null_report()returns a smallcolumn | nulls | pcttable;.print()logs it and returns the table.write_csv(path)writes the file.
Run it in Python#
No desktop needed — build a sample file, wire the three steps, and run with an
Engine:
from lsdtools import Engine, Source, Table
# make a sample CSV to read
Table({
"full_name": ["Zoe", "Ada", "Kai"],
"yrs": [17, 34, 22],
}).write_csv("people.csv")
src = load_people(Source.file("people.csv"))
Engine().run(write_clean(clean(src, min_age=18), path="clean.csv"))
# → null report prints; clean.csv now holds only Ada and Kai (Zoe is under 18), sorted by name
Table.read_csv("clean.csv").print()
# → name age
# Ada 34
# Kai 22
clean(src, min_age=18) wires the shape's port to the load node src; write_clean(...) wires the
deliver to the shape. Engine().run(...) executes and caches the chain.
Try changing it#
- Add a second deliver that keeps a Parquet copy:
t.write_parquet("clean.parquet"). - Drop rows with any null instead of a fixed age — filter on
t["age"].is_valid(). - Add a control view — a
Viewwith a slider formin_ageand a Run button.
Related#
Learn — Load steps · Shape steps · Deliver steps
Examples — File loader · Live node metrics