Skip to main content

Local-first data profiling and cleaning engine with reproducible, exportable pipelines

Project description

🧹 tidysheet

Local-first data profiling & cleaning with reproducible, exportable pipelines.
Point it at a messy CSV / Excel / Parquet / JSON file — it profiles the data, proposes a cleaning pipeline you review as plain YAML, runs it on DuckDB, and exports the whole thing as standalone pandas, Polars, or SQL code you own.

CI PyPI Latest release Python 3.10+ License: MIT

Engine: DuckDB Export: pandas · Polars · SQL Local-first

The tidysheet interactive UI

Your data never leaves your machine. Every change is an inspectable step. Nothing is ever fixed silently.

Install

uv tool install tidysheet      # or: pipx install tidysheet   (one command, isolated)
pip install tidysheet          # into the current environment

No Python? Download the standalone binary. Each release attaches a single-file executable (tidysheet-windows-x64.exe, tidysheet-macos-arm64, tidysheet-linux-x64) with no dependencies to install — grab it from the Releases page. Double-click it and the interactive UI opens in your browser — no terminal, no commands: just upload a file and start cleaning. Build it yourself with pyinstaller packaging/tidysheet.spec.

First-run OS warning (the binaries are unsigned): Windows — SmartScreen may say "Windows protected your PC" → More info → Run anyway. macOS — Gatekeeper may block it → right-click → Open (or xattr -d com.apple.quarantine tidysheet-macos-arm64 && chmod +x it). Linuxchmod +x tidysheet-linux-x64, then run it.

tidysheet open     data.csv                     # interactive UI at localhost
tidysheet profile  data.csv -o report.html      # what's in here, what's wrong
tidysheet suggest  data.csv -o pipeline.yaml    # proposed cleaning steps — review/edit
tidysheet infer    original.csv edited.csv -o pipeline.yaml   # reverse-engineer a pipeline from a hand-edit
tidysheet run      pipeline.yaml data.csv -o cleaned.csv
tidysheet run      pipeline.yaml next_months_file.csv -o cleaned2.csv --strict   # re-apply, fail on drift
tidysheet run      pipeline.yaml "months/*.csv" -o all.csv   # union a whole folder of monthly files
tidysheet check    --init cleaned.csv -o contract.yaml   # capture the shape you expect
tidysheet check    contract.yaml next_months_file.csv    # validate it — exit 1 on any violation
tidysheet watch    ./inbox -p pipeline.yaml --out-dir ./clean   # clean files as they land
tidysheet schedule pipeline.yaml data.csv -o clean.csv --every monthly  # emit a cron/CI/Task job
tidysheet export   pipeline.yaml --to pandas -o clean_data.py           # code you own
tidysheet diff     pipeline.yaml pipeline_v2.yaml           # semantic diff (exit 1 if changed)
tidysheet docs     pipeline.yaml -o pipeline.md             # readable docs for stakeholders

Already cleaning in Excel? Record it once (tidysheet infer)

Clean a file however you like — including by hand in Excel — then hand back both versions. tidysheet reverse-engineers the steps that reproduce your edits, verifies them (it replays the inferred pipeline on the original and checks it matches your edited file, cell by cell), and saves a pipeline you re-run forever:

tidysheet infer messy.xlsx messy_cleaned.xlsx -o pipeline.yaml
#   ✓ customer: stripped surrounding whitespace (12 cell(s))
#   ✓ country:  8 value replacement(s) (usa→United States, uk→United Kingdom, …)
#   ✓ status:   blanked null-like token(s) ['n/a'] (3 cell(s))
#   Reproduced 214/214 edited cell(s) (100%).

It detects trim, case, value maps, null-token blanking, dropped/renamed columns and de-duplication. Anything it can't explain (a one-off manual fix, an added row) is reported honestly as residual — never silently faked.

Optional AI for the residuals. Add --ai to let a model propose steps for what the deterministic layer couldn't explain — a local model with no new dependency, or Claude via the optional [ai] extra:

tidysheet infer messy.csv cleaned.csv -o pipeline.yaml --ai ollama   # 100% local
tidysheet infer messy.csv cleaned.csv -o pipeline.yaml --ai claude   # pip install tidysheet[ai]
tidysheet infer messy.csv cleaned.csv --ai claude --ai-dry-run       # print what would be sent; call nothing

The default is still no model. AI proposals pass back through the same verify gate — a step that doesn't reproduce your edit is rejected, so the model can suggest but never fake a result. --ai-dry-run prints the exact prompt (only column names, residual descriptions, and a few sample rows — never the full table) so you can see what would leave the machine before it does.

Guarded re-runs: drift never passes silently

When you suggest or infer a pipeline, tidysheet snapshots what the data looked like (the category values a step was built against). On every re-run it validates the new file against those snapshots and reports value drift loudly instead of cleaning silently:

  • a new category a map_values step never saw (passes through unmapped),
  • a value that silently coerces to NULL because it stopped parsing ("1.2K").

tidysheet run … --strict exits non-zero on drift, so scheduled re-runs fail loudly rather than hand back quietly-wrong data.

Data contracts (tidysheet check)

The drift guard protects one recorded pipeline. A data contract protects a dataset — a declarable statement of the shape you expect (schema, types, value ranges, allowed domains, null rates, uniqueness), validated on every new file. It's the CI guardrail for recurring jobs: exit non-zero the moment next period's data violates the shape you agreed to, instead of quietly passing it downstream.

Generate a starter contract from a known-good file — like suggest proposes a pipeline, check --init proposes a contract you review and edit:

tidysheet check --init cleaned.csv -o contract.yaml
tidysheet_contract:
  version: '0.1'
  name: cleaned
  rows: {min: 1}
  duplicates: {max: 0}
  columns:
    order_id: {type: integer, not_null: true, unique: true}
    amount:   {type: numeric, not_null: true}     # generation is conservative:
    country:  {type: text, semantic: country}     # it won't assert min:0 if the
    status:   {type: text, allowed: [Shipped, Pending, Cancelled, Refunded]}

Then validate any file against it:

tidysheet check contract.yaml next_months_file.csv
#   Contract check: 23/24 expectation(s) passed, 1 FAILED
#     ✗ status: allowed — 1 value(s) not allowed (1 row(s))
#         e.g. 'Bogus'
  • Exits 1 on any violation (use in CI / scheduled runs); --no-fail reports without failing; --json out.json writes machine-readable results.
  • Column expectations: required, type (integer / numeric / temporal / boolean / text), not_null, max_null_pct, unique, min / max, allowed (value domain), regex, semantic. Table-level: rows.min/max, duplicates.max.
  • Generation is conservative — it asserts only what the sample clearly shows (no nulls → not_null; a small closed vocabulary → allowed), so a starter contract rarely false-fails and is meant to be tightened by hand.
  • Like the drift guard, a contract never mutates data — it only observes and reports.

Recurring jobs (schedule / watch)

A pipeline that cleans one file is a script; a pipeline that cleans every month's file is the product. Two ways to make a saved pipeline recur:

Emit a scheduled job for whatever runs your cron:

tidysheet schedule pipeline.yaml data.csv -o cleaned.csv --every monthly --strict
#   0 8 1 * *  cd /work && tidysheet run pipeline.yaml data.csv -o cleaned.csv --strict

--to cron (default), --to github-actions (a full workflow that installs tidysheet, runs the pipeline, uploads the cleaned output, and — with --strict — fails the build on value drift), or --to windows (a schtasks command). --every daily|weekly|monthly + --at HH:MM set the cadence.

Or watch a folder and clean files as they arrive — no scheduler needed:

tidysheet watch ./inbox -p pipeline.yaml --out-dir ./clean
#   ✓ january.csv → january.cleaned.csv
#   ✓ february.csv → february.cleaned.csv  ⚠ drift

Every new or changed file matching --pattern (default *.csv) is run through the pipeline; unchanged files are skipped, and its own outputs are never re-ingested. --once sweeps the current contents and exits (handy in a job); --strict counts value drift as a failure. Dependency-free — it polls, so there's nothing extra to install.

The interactive UI (tidysheet open)

A local web app (FastAPI + a zero-dependency, no-build frontend served from the package; binds to 127.0.0.1 only):

  • Virtualized data grid — scrolls millions of rows via windowed fetching; click a column header for its stats, semantic type, issues, and facets.
  • Suggestion cards with confidence and evidence; every step previews before it applies: rows added/removed with samples, per-column before → after cell diffs, new-column samples.
  • Facet panel per column (value counts with bars; click a value to keep or drop those rows).
  • Cluster & merge review — OpenRefine-style: inspect each cluster of near-identical values, edit the canonical form, merge to a map_values step.
  • Step history — toggle steps on/off or remove them; downstream state recomputes; undo removes the last step. The history is the pipeline.
  • Column actions: trim, case, parse numbers/dates (auto format detection), cast, fill, split, rename, outlier flags, drop.
  • Export pipeline YAML / pandas / Polars / SQL and download cleaned CSV/Parquet, all from the toolbar. Light/dark follows the OS.

What the engine does

  • Multi-file input: profile, suggest, and run accept a single file, a glob (data/*.csv), or several schema-compatible files — unioned in a deterministic order (union_by_name tolerates column reordering), so a folder of monthly extracts profiles or cleans in one command. DuckDB under the hood means CSV / Parquet / JSON / Excel all work the same way.
  • Fast profiling on DuckDB: types, missing values, distincts, stats, duplicates, whitespace/case issues, null-like tokens (N/A, -, …), IQR outliers — plus a self-contained HTML report (works offline, light/dark).
  • Semantic type detection (deterministic, no AI): emails, URLs, phones, zips, identifiers, booleans-as-text, numbers-as-text (currency $1,234.50, percents 12.5%, parenthesized negatives, locale decimal commas), dates-as-text with multi-format detection (2024-01-15 + 15/01/2024 in one column), countries and US states via bundled reference tables.
  • Rule-based suggestions with evidence and confidence on every step — including OpenRefine-style fuzzy value clustering ("Shipped", "shipped ", "SHIPPED" → one canonical value) that runs on distinct values via DuckDB, so it scales past OpenRefine's in-memory ceiling.
  • A declarative pipeline IR (YAML): 18 ops, validated against the source schema before execution, git-diffable, re-appliable. Schema drift on re-apply fails loudly, never silently.
  • Code export to pandas, Polars, and DuckDB SQL — standalone scripts, no tidysheet dependency, verified by a differential test suite to reproduce the engine's output exactly.
  • Explainable quality score (completeness / validity / consistency / uniqueness) — every number decomposes into named issues; no black-box scores.
  • A cleaning changelog on every run: rows and columns affected per step.
  • Review & document: tidysheet diff gives a semantic, param-level diff of two pipelines (aligned by step id; exits non-zero on change — a CI signal), and tidysheet docs renders a pipeline as Markdown or HTML you can hand to a stakeholder instead of the YAML. Both are pure reads — nothing executes.

Principles (from the product plan)

  1. The pipeline is the product. Chat tools clean a file once; a pipeline cleans every month's file. Everything serializes, diffs, and re-runs.
  2. Suggest, preview, approve. The engine never auto-fixes. Outliers are flagged, not deleted. Every suggested step carries its evidence.
  3. Deterministic core; AI as optional suggester. Everything above runs offline with no model. An LLM may only plug into one seam (tidysheet.suggester): it sees a small, data-light context, only proposes IR steps for the residuals the deterministic layer can't explain, and those proposals pass back through the same verify gate. The default is a no-op, so the core keeps its "data never leaves this machine" promise and is never required. Bring your own model — the core pyproject.toml depends on no provider; a local --ai ollama needs nothing, and --ai claude is the optional [ai] extra.
  4. No lock-in. The export is the escape hatch: if you stop using the tool tomorrow, you keep working code.

Pipeline format

tidysheet_pipeline:
  version: '0.1'
  name: messy_sales_clean
  source: {path: demo/messy_sales.csv, schema: [...]}
  steps:
  - id: 3f2a9c1d
    op: map_values
    params:
      column: country
      mapping: {usa: United States, U.S.: United States, DE: Germany}
    provenance: rule          # user | rule | ai — you always know who proposed it
    note: 'standardize country names: 16 variant(s) mapped to canonical form'
    confidence: 0.85

Ops in v0.1: standardize_missing, trim_whitespace, change_case, find_replace, regex_extract, split_column, rename_columns, drop_columns, filter_rows, cast_type, fill_missing, drop_missing, deduplicate, parse_dates, parse_numbers, date_decompose, flag_outliers, map_values — run tidysheet ops.

Try the demo

tidysheet suggest demo/messy_sales.csv -o demo/pipeline.yaml
tidysheet run demo/pipeline.yaml demo/messy_sales.csv -o demo/cleaned.csv
tidysheet export demo/pipeline.yaml --to sql

Development

python -m venv .venv && .venv/Scripts/pip install -e .[dev]
.venv/Scripts/python -m pytest

The test suite includes a differential export suite: every suggested pipeline is executed by the engine and by the exported pandas / Polars / SQL code, and outputs are compared cell-by-cell. That contract is the point of the project; keep it green.

Status & roadmap

This covers roadmap Phases 1–4 of RESEARCH_AND_IMPLEMENTATION_PLAN.md (profiling engine, cleaning engine + IR runtime, interactive UI, pipeline export), plus recurrence hardening — value-drift checks on re-run (--strict), diff-inference (tidysheet infer) that turns a before/after pair into a verified pipeline, and data contracts (tidysheet check) that validate next period's file against a declarable shape. The AI seam is in place (tidysheet.suggester, no-op by default) and now ships two opt-in adapters — a local Ollama backend (no dependency) and a Claude backend (the [ai] extra) — that only propose steps for residuals, still gated by verify.

The ROADMAP.md post-0.2 features are all in: data contracts (check), zero-setup install (uv/pipx + a standalone binary), recurring jobs (schedule / watch), the BYO-model AI suggester (Ollama + Claude), multi-file / glob input, and pipeline diff & docs (diff / docs). Next up: database & S3 connectors (via DuckDB's postgres / sqlite / httpfs extensions) and schema-drift remapping.

MIT license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tidysheet-0.3.0.tar.gz (109.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tidysheet-0.3.0-py3-none-any.whl (100.7 kB view details)

Uploaded Python 3

File details

Details for the file tidysheet-0.3.0.tar.gz.

File metadata

  • Download URL: tidysheet-0.3.0.tar.gz
  • Upload date:
  • Size: 109.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tidysheet-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b8b1fcacf33b25aef07d46082c26d44277ca32369175799ae7eac6375e305ce1
MD5 d63bb8fb9156b36e57219f2b1feba3a1
BLAKE2b-256 5738af230a1168814648de818795f8bf3f4acd7c2aa25339d3ba6fbfc04ac9a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for tidysheet-0.3.0.tar.gz:

Publisher: publish-pypi.yml on rohanbeingsocial/tidysheet

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tidysheet-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: tidysheet-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 100.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tidysheet-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5fdb66c8aaeadbb42ada0a549d0aa2735d032b2b62e9f00d49a277229a051a2
MD5 fac91712c5a7b316f759c34dc8838538
BLAKE2b-256 94fa87b02b594c471430e7c2e622b13eab9b23a968fd6d2086d9a5598c459edf

See more details on using hashes here.

Provenance

The following attestation bundles were made for tidysheet-0.3.0-py3-none-any.whl:

Publisher: publish-pypi.yml on rohanbeingsocial/tidysheet

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page