Skip to main content

duckboard

File-first local SQL workspace for CSV, Parquet, PSV, and JSON — powered by DuckDB.

Load files once, query by name with plain SQL, export results. Terminal-native alternative to spinning up a notebook for quick file questions.

Status: v0.3.0 — available on PyPI.

Install

pip install duckboard

Development install:

git clone https://github.com/OmUniyal/duckboard
cd duckboard
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"

Quickstart

duckboard
duckboard> :load examples/sample.csv as sample
duckboard> SELECT color, COUNT(*) AS n FROM sample GROUP BY 1;
┌────────┬───┐
│ color  │ n │
├────────┼───┤
│ blue   │ 1 │
│ green  │ 1 │
│ red    │ 2 │
└────────┴───┘
(3 rows)
duckboard> :save results.csv
Saved 3 rows to results.csv
duckboard> :quit
Bye.

Supported file formats

Extension Format
.csv, .tsv CSV
.psv PSV (pipe-separated)
.parquet Parquet
.json, .jsonl, .ndjson JSON

REPL commands

Command Description
:load "path" [as name] [--no-header] Load a file as a queryable table. Name defaults to filename stem. Use --no-header if the file has no header row — duckboard will prompt for column names or auto-generate them.
:tables List all loaded tables with format, path, and validation status.
:schema <table> Show column names, types, and nullability. Includes load warnings when present.
:save "path" [--csv|--parquet|--json] Save last query result to a file. Format auto-detected from extension; use flag to override.
:unload <table> Remove a loaded table from the session.
:unload all Remove every loaded table in one shot.
:rename_column table old_name new_name Rename a column in a loaded table.
:export_errors table "path" [--csv|--parquet|--json] Export rows that failed validation to a file.
:export_clean table "path" [--csv|--parquet|--json] Export only validated rows to a file.
:pwd Show current working directory.
:clear / :cls Clear the terminal.
:quit / :q / exit / quit / Ctrl+D Exit duckboard.

Vertical output

Wide results can be viewed one row at a time using vertical mode. Two ways to trigger it:

\G suffix — append immediately before the semicolon:

duckboard> SELECT * FROM orders WHERE id = 1\G;
*************************** 1. row ***************************
        id: 1
  customer: Alice
   revenue: 12345.67

(1 row)

Oracle-style hint — inline, with a custom display row cap:

SELECT /*+ vertical_result(5) */ * FROM orders;

This shows up to 5 rows vertically. The full result set is still held in memory for :save.

Tab autocomplete

duckboard supports tab completion (requires pyreadline3 on Windows, built-in readline on Linux/macOS):

  • Command names — type :s and press Tab to expand to :schema, :save, etc.
  • Table names — after :schema, :unload, :export_errors, and similar commands
  • File paths — after :load
  • SQL keywords — SEL → SELECT, FR → FROM, etc.

Install the readline extra on Windows:

pip install duckboard[readline]

Data quality

When a file is loaded, duckboard validates it and reports issues automatically.

Full-file structural validation (CSV/PSV/TSV): scans every row, flags column count mismatches into _errors_{name}. No row limit — a bad row at position 15,000 is caught just like one at position 5.

Type anomaly detection: flags rows where a numeric value appears in a predominantly string column (e.g. a number in a gender column).

Residual row-count cross-check: after loading, duckboard compares the raw line count against what DuckDB actually loaded. Any shortfall beyond known errors is surfaced as a warning with exact counts so the root cause can be tracked down.

:load output reflects what was found:

# Clean
Loaded 'orders' from orders.csv  (csv)

# Known validation errors
Loaded 'orders' from orders.csv  (csv)
  2 validation error(s) found.
  → :export_errors orders  to inspect  |  :export_clean orders  for clean rows

# DuckDB dropped rows silently (cause unknown)
Loaded 'orders' from orders.csv  (csv)
  2 row(s) were dropped by DuckDB but not captured in the error table
  (cause unknown — may be encoding, embedded nulls, or a DuckDB type coercion issue).
  Raw line count: 20000, loaded: 19998, known errors: 0.

All known errors are stored in _errors_{name} for the duration of the session:

SELECT * FROM _errors_orders;

The :tables command shows status for every loaded table:

┌────────┬────────┬────────────┬────────────┐
│ name   │ format │ path       │ errors     │
├────────┼────────┼────────────┼────────────┤
│ orders │ csv    │ orders.csv │ [!2]       │
│ events │ parquet│ events.prq │ [!?]       │
│ mixed  │ csv    │ mixed.csv  │ [!2 +?]    │
│ sample │ csv    │ sample.csv │ ok         │
└────────┴────────┴────────────┴────────────┘

[!N] — N known errors in the error table, exportable via :export_errors. [!?] — rows dropped by DuckDB, not in error table, cause TBD. [!N +?] — both.

Notes

  • Queries display a maximum of 50 rows in the terminal. Full results are always exported via :save.
  • Large exports (2,000+ rows) prompt for confirmation before writing.
  • On Windows, use forward slashes in paths: :load data/sales.csv not :load data\sales.csv.
  • Multi-line SQL is supported — statements execute on semicolon.
  • Validation runs on CSV and PSV files only. Parquet and JSON validation is planned for v0.4.0.
  • python -m duckboard works as an alternative launch method if the script entry point is unavailable.

Read more

I built a terminal SQL workspace for CSV files — and made it catch bad data automatically — dev.to

Project layout

src/duckboard/
├── __main__.py     # Enables python -m duckboard
├── session.py      # DuckboardSession — owns DuckDB connection + state
├── catalog.py      # Registered file → view mappings, validation, cross-check
├── repl.py         # Interactive REPL loop with \G and hint intercept
├── commands.py     # All REPL commands
├── formatter.py    # Box-drawing output, truncation, and vertical mode
├── completer.py    # Tab autocomplete for commands, tables, paths, SQL keywords
├── cli.py          # CLI entry point
└── exceptions.py   # DuckboardError hierarchy

License

MIT

Metadata

Release files for duckboard 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for duckboard 0.3.0
File Size Uploaded
duckboard-0.3.0.tar.gz 27.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for duckboard 0.3.0
File Interpreter ABI Platform
duckboard-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.4 kB

Release files / duckboard-0.3.0.tar.gz

Download URL duckboard-0.3.0.tar.gz
Size 27.1 kB
Tags Source
SHA-256 checksum
How to use checksums
697ec4c4a6dd707dc2686a83250b3d52a062f680608ef0d006c22276c39c5ead
BLAKE2b-256 checksum
How to use checksums
00fd32a1a6c123feed6948237620ce2305bdfa23435769d985fa292d27939b29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.9

Release files / duckboard-0.3.0-py3-none-any.whl

Download URL duckboard-0.3.0-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c8f8d34dc161c755b329d0c41b492b93b22ac27d4094f8591bf034fffd4f3d08
BLAKE2b-256 checksum
How to use checksums
ddabfc4fdc7b1faf6e69a76af57ca150eb5e2f3fb9fcffaa76f13835cdf44d1a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.9

Release history Release notifications | RSS feed

0.4.0

2 release files

0.3.1

2 release files

This release

0.3.0 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page